@Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class CloudAnalyticsClientTests {

    protected CloudAnalyticsClient client;
    protected CloudBlobContainer container;

    @Before
    public void analyticsTestMethodSetUp() throws URISyntaxException, StorageException {
        this.container = AnalyticsTestHelper.getRandomContainerReference();
        this.client = AnalyticsTestHelper.createCloudAnalyticsClient();
    }

    @After
    public void analyticsTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    /**
     * Test table getters.
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCloudAnalyticsClientGetTables() throws URISyntaxException, StorageException {
        CloudTable blobHourPrimary = this.client.getHourMetricsTable(StorageService.BLOB);
        CloudTable blobHourSecondary = this.client.getHourMetricsTable(StorageService.BLOB, StorageLocation.SECONDARY);
        CloudTable fileHourPrimary = this.client.getHourMetricsTable(StorageService.FILE);
        CloudTable fileHourSecondary = this.client.getHourMetricsTable(StorageService.FILE, StorageLocation.SECONDARY);
        CloudTable queueHourPrimary = this.client.getHourMetricsTable(StorageService.QUEUE, StorageLocation.PRIMARY);
        CloudTable queueHourSecondary = this.client.getHourMetricsTable(StorageService.QUEUE, StorageLocation.SECONDARY);
        CloudTable tableHourPrimary = this.client.getHourMetricsTable(StorageService.TABLE, StorageLocation.PRIMARY);
        CloudTable tableHourSecondary = this.client.getHourMetricsTable(StorageService.TABLE, StorageLocation.SECONDARY);

        CloudTable blobMinutePrimary = this.client.getMinuteMetricsTable(StorageService.BLOB);
        CloudTable blobMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.BLOB, StorageLocation.SECONDARY);
        CloudTable fileMinutePrimary = this.client.getMinuteMetricsTable(StorageService.FILE);
        CloudTable fileMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.FILE, StorageLocation.SECONDARY);
        CloudTable queueMinutePrimary = this.client.getMinuteMetricsTable(StorageService.QUEUE, StorageLocation.PRIMARY);
        CloudTable queueMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.QUEUE, StorageLocation.SECONDARY);
        CloudTable tableMinutePrimary = this.client.getMinuteMetricsTable(StorageService.TABLE, StorageLocation.PRIMARY);
        CloudTable tableMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.TABLE, StorageLocation.SECONDARY);

        CloudTable capacity = this.client.getCapacityTable();

        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_BLOB, blobHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_BLOB, blobHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_FILE, fileHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_FILE, fileHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_QUEUE, queueHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_QUEUE, queueHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_TABLE, tableHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_TABLE, tableHourSecondary.getName());

        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_BLOB, blobMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_BLOB, blobMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_FILE, fileMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_FILE, fileMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_QUEUE, queueMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_QUEUE, queueMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_TABLE, tableMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_TABLE, tableMinuteSecondary.getName());

        assertEquals(Constants.AnalyticsConstants.METRICS_CAPACITY_BLOB, capacity.getName());
    }

    /**
     * List all logs
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    public void testCloudAnalyticsClientListLogs() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 13;
        Calendar now = new GregorianCalendar();

        now.add(GregorianCalendar.MONTH, -13);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 13, now,
                Granularity.MONTH);

        assertEquals(numBlobs, blobNames.size());

        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 0);
    }

    /**
     * List Logs with open ended time range
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientListLogsStartTime() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 48;
        Calendar now = new GregorianCalendar();
        now.add(GregorianCalendar.DAY_OF_MONTH, -2);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 48, now,
                Granularity.HOUR);

        assertEquals(numBlobs, blobNames.size());

        Calendar start = new GregorianCalendar();
        start.add(GregorianCalendar.DAY_OF_MONTH, -1);
        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB, start.getTime(), null, null, null, null,
                null)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 24);
    }

    /**
     * List Logs with well defined time range
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientListLogsStartEndTime() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 72;
        Calendar now = new GregorianCalendar();
        now.add(GregorianCalendar.DAY_OF_MONTH, -3);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 72, now,
                Granularity.HOUR);

        assertEquals(numBlobs, blobNames.size());

        Calendar start = new GregorianCalendar();
        start.add(GregorianCalendar.DAY_OF_MONTH, -2);
        Calendar end = new GregorianCalendar();
        end.add(GregorianCalendar.DAY_OF_MONTH, -1);
        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB, start.getTime(), end.getTime(), null,
                null, null, null)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 48);
    }

    /**
     * Validate Log Parser
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseExLogs() throws ParseException, URISyntaxException, StorageException,
            IOException {
        String logText = "1.0;2011-08-09T18:52:40.9241789Z;GetBlob;AnonymousSuccess;200;18;10;anonymous;;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumb&amp;nails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";a84aa705-8a85-48c5-b064-b43bd22979c3;0;123.100.2.10;2009-09-19;252;0;265;100;0;;;\"0x8CE1B6EA95033D5\";Tuesday, 09-Aug-11 18:52:40 GMT;;;;\"8/9/2011 6:52:40 PM ba98eb12-700b-4d53-9230-33a3330571fc\""
                + '\n'
                + "1.0;2011-08-09T18:02:40.6271789Z;PutBlob;Success;201;28;21;authenticated;myaccount;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";fb658ee6-6123-41f5-81e2-4bfdc178fea3;0;201.9.10.20;2009-09-19;438;100;223;0;100;;\"66CbMXKirxDeTr82SXBKbg==\";\"0x8CE1B67AD25AA05\";Tuesday, 09-Aug-11 18:02:40 GMT;;;;\"8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32\""
                + '\n';
        this.container.createIfNotExists();
        CloudBlockBlob blob = this.container.getBlockBlobReference("blob1");
        blob.uploadText(logText);
        Iterator<LogRecord> iterator = CloudAnalyticsClient.parseLogBlob(blob).iterator();

        assertTrue(iterator.hasNext());
        LogRecord actualItemOne = iterator.next();
        assertTrue(iterator.hasNext());
        LogRecord actualItemTwo = iterator.next();

        LogRecord expectedItemOne = new LogRecord();
        expectedItemOne.setVersionNumber("1.0");
        expectedItemOne.setRequestStartTime(LogRecord.REQUEST_START_TIME_FORMAT.parse("2011-08-09T18:52:40.9241789Z"));
        expectedItemOne.setOperationType("GetBlob");
        expectedItemOne.setRequestStatus("AnonymousSuccess");
        expectedItemOne.setHttpStatusCode("200");
        expectedItemOne.setEndToEndLatencyInMS(18);
        expectedItemOne.setServerLatencyInMS(10);
        expectedItemOne.setAuthenticationType("anonymous");
        expectedItemOne.setRequesterAccountName(null);
        expectedItemOne.setOwnerAccountName("myaccount");
        expectedItemOne.setServiceType("blob");
        expectedItemOne.setRequestUrl(new URI(
                "https://myaccount.blob.core.windows.net/thumb&nails/lake.jpg?timeout=30000"));
        expectedItemOne.setRequestedObjectKey("/myaccount/thumbnails/lake.jpg");
        expectedItemOne.setRequestIdHeader(UUID.fromString("a84aa705-8a85-48c5-b064-b43bd22979c3"));
        expectedItemOne.setOperationCount(0);
        expectedItemOne.setRequesterIPAddress("123.100.2.10");
        expectedItemOne.setRequestVersionHeader("2009-09-19");
        expectedItemOne.setRequestHeaderSize(252L);
        expectedItemOne.setRequestPacketSize(0L);
        expectedItemOne.setResponseHeaderSize(265L);
        expectedItemOne.setResponsePacketSize(100L);
        expectedItemOne.setRequestContentLength(0L);
        expectedItemOne.setRequestMD5(null);
        expectedItemOne.setServerMD5(null);
        expectedItemOne.setETagIdentifier("0x8CE1B6EA95033D5");
        expectedItemOne.setLastModifiedTime(LogRecord.LAST_MODIFIED_TIME_FORMAT
                .parse("Tuesday, 09-Aug-11 18:52:40 GMT"));
        expectedItemOne.setConditionsUsed(null);
        expectedItemOne.setUserAgentHeader(null);
        expectedItemOne.setReferrerHeader(null);
        expectedItemOne.setClientRequestId("8/9/2011 6:52:40 PM ba98eb12-700b-4d53-9230-33a3330571fc");

        LogRecord expectedItemTwo = new LogRecord();
        expectedItemTwo.setVersionNumber("1.0");
        expectedItemTwo.setRequestStartTime(LogRecord.REQUEST_START_TIME_FORMAT.parse("2011-08-09T18:02:40.6271789Z"));
        expectedItemTwo.setOperationType("PutBlob");
        expectedItemTwo.setRequestStatus("Success");
        expectedItemTwo.setHttpStatusCode("201");
        expectedItemTwo.setEndToEndLatencyInMS(28);
        expectedItemTwo.setServerLatencyInMS(21);
        expectedItemTwo.setAuthenticationType("authenticated");
        expectedItemTwo.setRequesterAccountName("myaccount");
        expectedItemTwo.setOwnerAccountName("myaccount");
        expectedItemTwo.setServiceType("blob");
        expectedItemTwo.setRequestUrl(new URI(
                "https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000"));
        expectedItemTwo.setRequestedObjectKey("/myaccount/thumbnails/lake.jpg");
        expectedItemTwo.setRequestIdHeader(UUID.fromString("fb658ee6-6123-41f5-81e2-4bfdc178fea3"));
        expectedItemTwo.setOperationCount(0);
        expectedItemTwo.setRequesterIPAddress("201.9.10.20");
        expectedItemTwo.setRequestVersionHeader("2009-09-19");
        expectedItemTwo.setRequestHeaderSize(438L);
        expectedItemTwo.setRequestPacketSize(100L);
        expectedItemTwo.setResponseHeaderSize(223L);
        expectedItemTwo.setResponsePacketSize(0L);
        expectedItemTwo.setRequestContentLength(100L);
        expectedItemTwo.setRequestMD5(null);
        expectedItemTwo.setServerMD5("66CbMXKirxDeTr82SXBKbg==");
        expectedItemTwo.setETagIdentifier("0x8CE1B67AD25AA05");
        expectedItemTwo.setLastModifiedTime(LogRecord.LAST_MODIFIED_TIME_FORMAT
                .parse("Tuesday, 09-Aug-11 18:02:40 GMT"));
        expectedItemTwo.setConditionsUsed(null);
        expectedItemTwo.setUserAgentHeader(null);
        expectedItemTwo.setReferrerHeader(null);
        expectedItemTwo.setClientRequestId("8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32");

        CloudAnalyticsClientTests.assertLogItemsEqual(expectedItemOne, actualItemOne);
        CloudAnalyticsClientTests.assertLogItemsEqual(expectedItemTwo, actualItemTwo);
    }

    /**
     * Validate Log Parser with prod data
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseProdLogs() throws ParseException, URISyntaxException, StorageException,
            IOException {

        Calendar startTime = new GregorianCalendar();
        startTime.add(GregorianCalendar.HOUR_OF_DAY, -2);

        Iterator<LogRecord> logRecordsIterator = (this.client.listLogRecords(StorageService.BLOB, startTime.getTime(),
                null, null, null)).iterator();

        while (logRecordsIterator.hasNext()) {
            // Makes sure there's no exceptions thrown and that no records are null.
            // Primarily a sanity check.
            LogRecord rec = logRecordsIterator.next();
            System.out.println(rec.getRequestUrl());
            assertNotNull(rec);
        }
    }

    /**
     * Log parser error cases.
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseLogErrors() throws ParseException, URISyntaxException, StorageException,
            IOException {
        this.container.createIfNotExists();

        String v2Entry = "2.0;2011-08-09T18:02:40.6271789Z;PutBlob;Success;201;28;21;authenticated;myaccount;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";fb658ee6-6123-41f5-81e2-4bfdc178fea3;0;201.9.10.20;2009-09-19;438;100;223;0;100;;\"66CbMXKirxDeTr82SXBKbg==\";\"0x8CE1B67AD25AA05\";Tuesday, 09-Aug-11 18:02:40 GMT;;;;\"8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32\"" + '\n';
        CloudBlockBlob v2Blob = this.container.getBlockBlobReference("v2Blob");
        v2Blob.uploadText(v2Entry);
        Iterator<LogRecord> v2Iterator = CloudAnalyticsClient.parseLogBlob(v2Blob).iterator();
        try {
            v2Iterator.next();
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals(e.getMessage(), "A storage log version of 2.0 is unsupported.");
        }

        // Note that if this non log data ('%s') contains a semicolon, we'll get an unfortunate failure
        // saying that a log version of %s is not supported. Otherwise, we'll see the behavior below.
        String nonLogData = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG 1234567890";
        CloudBlockBlob nonLogDataBlob = this.container.getBlockBlobReference("nonLogBlob");
        nonLogDataBlob.uploadText(nonLogData);
        Iterator<LogRecord> nonLogDataIterator = CloudAnalyticsClient.parseLogBlob(nonLogDataBlob).iterator();
        try {
            nonLogDataIterator.next();
            fail();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(),
                    "An error occurred while enumerating the result, check the original exception for details.");
            assertEquals(e.getCause().getMessage(), "Error parsing log record: unexpected end of stream.");
        }

        CloudBlockBlob nullBlob1 = this.container.getBlockBlobReference("nullBlob1");
        CloudBlockBlob nullBlob2 = this.container.getBlockBlobReference("nullBlob2");
        CloudBlockBlob nullBlob3 = this.container.getBlockBlobReference("nullBlob3");
        ArrayList<ListBlobItem> nullBlobIterator = new ArrayList<ListBlobItem>();
        nullBlobIterator.add(nullBlob1);
        nullBlobIterator.add(nullBlob2);
        nullBlobIterator.add(nullBlob3);
        Iterator<LogRecord> nullLogIterator = CloudAnalyticsClient.parseLogBlobs(nullBlobIterator).iterator();
        try {
            nullLogIterator.next();
            fail();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(),
                    "An error occurred while enumerating the result, check the original exception for details.");
            assertEquals(e.getCause().getMessage(), "The specified blob does not exist.");
        }

        try {
            Iterator<LogRecord> emptyIterator = CloudAnalyticsClient.parseLogBlobs(new ArrayList<ListBlobItem>()).iterator();
            assertFalse(emptyIterator.hasNext());
            emptyIterator.next();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(), "There are no more elements in this enumeration.");
        }

        try {
            CloudAnalyticsClient.parseLogBlobs(null);
        } catch (IllegalArgumentException e) {
            assertEquals(e.getMessage(), "The argument must not be null or an empty string. Argument name: logBlobs.");
        }
    }

    public static void assertLogItemsEqual(LogRecord expected, LogRecord actual) {
        assertEquals(expected.getVersionNumber(), actual.getVersionNumber());

        assertEquals(expected.getRequestStartTime(), actual.getRequestStartTime());
        assertEquals(expected.getOperationType(), actual.getOperationType());
        assertEquals(expected.getRequestStatus(), actual.getRequestStatus());
        assertEquals(expected.getHttpStatusCode(), actual.getHttpStatusCode());
        assertEquals(expected.getEndToEndLatencyInMS(), actual.getEndToEndLatencyInMS());
        assertEquals(expected.getServerLatencyInMS(), actual.getServerLatencyInMS());
        assertEquals(expected.getAuthenticationType(), actual.getAuthenticationType());
        assertEquals(expected.getRequesterAccountName(), actual.getRequesterAccountName());
        assertEquals(expected.getOwnerAccountName(), actual.getOwnerAccountName());
        assertEquals(expected.getServiceType(), actual.getServiceType());
        assertEquals(expected.getRequestUrl(), actual.getRequestUrl());
        assertEquals(expected.getRequestedObjectKey(), actual.getRequestedObjectKey());
        assertEquals(expected.getRequestIdHeader(), actual.getRequestIdHeader());
        assertEquals(expected.getOperationCount(), actual.getOperationCount());
        assertEquals(expected.getRequesterIPAddress(), actual.getRequesterIPAddress());
        assertEquals(expected.getRequestVersionHeader(), actual.getRequestVersionHeader());
        assertEquals(expected.getRequestHeaderSize(), actual.getRequestHeaderSize());
        assertEquals(expected.getRequestPacketSize(), actual.getRequestPacketSize());
        assertEquals(expected.getResponseHeaderSize(), actual.getResponseHeaderSize());
        assertEquals(expected.getResponsePacketSize(), actual.getResponsePacketSize());
        assertEquals(expected.getRequestContentLength(), actual.getRequestContentLength());
        assertEquals(expected.getRequestMD5(), actual.getRequestMD5());
        assertEquals(expected.getServerMD5(), actual.getServerMD5());
        assertEquals(expected.getETagIdentifier(), actual.getETagIdentifier());
        assertEquals(expected.getLastModifiedTime(), actual.getLastModifiedTime());
        assertEquals(expected.getConditionsUsed(), actual.getConditionsUsed());
        assertEquals(expected.getUserAgentHeader(), actual.getUserAgentHeader());
        assertEquals(expected.getReferrerHeader(), actual.getReferrerHeader());
        assertEquals(expected.getClientRequestId(), actual.getClientRequestId());
    }
} @Category({ DevFabricTests.class, CloudTests.class })
public class CloudAppendBlobTests {
    protected CloudBlobContainer container;

    @Before
    public void appendBlobTestMethodSetUp() throws URISyntaxException,
            StorageException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();
    }

    @After
    public void appendBlobTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    /**
     * Create an append blob.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testAppendBlobCreate() throws StorageException, URISyntaxException {
        final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));

        assertFalse(blob.exists());

        // Create
        blob.createOrReplace();
        assertTrue(blob.exists());

        // Create again (should succeed)
        blob.createOrReplace();
        assertTrue(blob.exists());

        // Create again, specifying not to if it already exists
        // This should fail
        // Add 15 min to account for clock skew
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        cal.add(Calendar.MINUTE, 15);
        AccessCondition condition = AccessCondition.generateIfModifiedSinceCondition(cal.getTime());

        try {
            blob.createOrReplace(condition, null, null);
            fail("Create should fail due to access condition.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage());
            assertEquals("ConditionNotMet", ex.getErrorCode());
        }
        
        // Create again, specifying not to if it already exists
        // This should fail
        condition = AccessCondition.generateIfNotExistsCondition();
        try {
            blob.createOrReplace(condition, null, null);
            fail("Create should fail due to access condition.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
            assertEquals("The specified blob already exists.", ex.getMessage());
            assertEquals("BlobAlreadyExists", ex.getErrorCode());
        }
    }

    /**
     * Delete an append blob.
     * 
     * @throws StorageException
     * @throws URISyntaxException 
     */
    @Test
    public void testAppendBlobDelete() throws StorageException, URISyntaxException {
        final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));
        
        assertFalse(blob.exists());
        
        // create
        blob.createOrReplace();
        assertTrue(blob.exists());
        
        // delete
        blob.delete();
        assertFalse(blob.exists());
        
        // delete again, should fail as it doesn't exist
        try {
            blob.delete();
            fail("Delete should fail as blob does not exist.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
            assertEquals("The specified blob does not exist.", ex.getMessage());
            assertEquals("BlobNotFound", ex.getErrorCode());
        }                     
    }
    
    /**
     * Delete an append blob if it exists.
     * 
     * @throws StorageException
     * @throws URISyntaxException 
     */
    @Test
    public void testAppendBlobDeleteIfExists() throws URISyntaxException, StorageException {
        final CloudAppendBlob blob = this.container.getAppendBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));

        assertFalse(blob.exists());
        assertFalse(blob.deleteIfExists());

        blob.createOrReplace();
        assertTrue(blob.exists());

        assertTrue(blob.deleteIfExists());
        assertFalse(blob.deleteIfExists());

        // check if second condition works in delete if exists
        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

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

        // The second delete of a blob will return a 404
        blob.createOrReplace();
        assertFalse(blob.deleteIfExists(DeleteSnapshotsOption.NONE, null, null, ctx));
    }

    /**
     * Start copying a blob and then abort
     */
    @Test
    public void testCopyFromAppendBlobAbortTest() throws StorageException,
            URISyntaxException, IOException {
        final int length = 512;
        CloudAppendBlob originalBlob = (CloudAppendBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.APPEND_BLOB, "originalBlob", length, null);
        CloudAppendBlob copyBlob = this.container.getAppendBlobReference(originalBlob.getName() + "copyed");
        copyBlob.startCopy(originalBlob);

        try {
            copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId());
        } catch (StorageException e) {
            if (!e.getErrorCode().contains("NoPendingCopyOperation")) {
                throw e;
            }
        }
    }

    /**
     * Create a snapshot
     */
    @Test
    public void testAppendBlobSnapshotValidationTest() throws StorageException,
            URISyntaxException, IOException {
        final int length = 1024;
        CloudAppendBlob appendBlobRef = (CloudAppendBlob) BlobTestHelper
                .uploadNewBlob(this.container, BlobType.APPEND_BLOB,
                        "originalBlob", length, null);
        final CloudBlob blobSnapshot = appendBlobRef.createSnapshot();

        for (ListBlobItem blob : this.container.listBlobs(null, true,
                EnumSet.allOf(BlobListingDetails.class), null, null)) {
            final ByteArrayOutputStream outStream = new ByteArrayOutputStream(
                    length);
            ((CloudBlob) blob).download(outStream);
        }

        ByteArrayOutputStream outStream = new ByteArrayOutputStream(length);

        blobSnapshot.download(outStream);
        byte[] retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);

        // Read operation should work fine.
        blobSnapshot.downloadAttributes();

        final CloudAppendBlob blobSnapshotUsingRootUri = this.container
                .getAppendBlobReference(appendBlobRef.getName(),
                        blobSnapshot.getSnapshotID());
        outStream = new ByteArrayOutputStream(length);

        blobSnapshotUsingRootUri.download(outStream);
        retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);
        assertEquals(blobSnapshot.getSnapshotID(),
                blobSnapshotUsingRootUri.getSnapshotID());

        // Expect an IllegalArgumentException from upload.
        try {
            final Random randGenerator = new Random();
            final byte[] buff = new byte[length];
            randGenerator.nextBytes(buff);
            blobSnapshot.upload(new ByteArrayInputStream(buff), -1);
            fail("Expect an IllegalArgumentException from upload");
        } catch (IllegalArgumentException e) {
            assertEquals(
                    "Cannot perform this operation on a blob representing a snapshot.",
                    e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadMetadata.
        try {
            blobSnapshot.uploadMetadata();
            fail("Expect an IllegalArgumentException from uploadMetadata");
        } catch (IllegalArgumentException e) {
            assertEquals(
                    "Cannot perform this operation on a blob representing a snapshot.",
                    e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadProperties.
        try {
            blobSnapshot.uploadProperties();
            fail("Expect an IllegalArgumentException from uploadProperties");
        } catch (IllegalArgumentException e) {
            assertEquals(
                    "Cannot perform this operation on a blob representing a snapshot.",
                    e.getMessage());
        }

        // Expect an IllegalArgumentException from createSnapshot.
        try {
            blobSnapshot.createSnapshot();
            fail("Expect an IllegalArgumentException from createSnapshot");
        } catch (IllegalArgumentException e) {
            assertEquals(
                    "Cannot perform this operation on a blob representing a snapshot.",
                    e.getMessage());
        }
    }

    /**
     * Create a blob and try to download a range of its contents
     */
    @Test
    public void testAppendBlobDownloadRangeValidationTest()
            throws StorageException, URISyntaxException, IOException {
        final int length = 5 * 1024 * 1024;

        final String appendBlobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudAppendBlob appendBlobRef = this.container
                .getAppendBlobReference(appendBlobName);

        appendBlobRef
                .upload(BlobTestHelper.getRandomDataStream(length), length);

        // Download full blob
        appendBlobRef.download(new ByteArrayOutputStream());
        assertEquals(length, appendBlobRef.getProperties().getLength());

        // Download blob range.
        byte[] downloadBuffer = new byte[100];
        int downloadLength = appendBlobRef.downloadRangeToByteArray(0,
                (long) 100, downloadBuffer, 0);
        assertEquals(length, appendBlobRef.getProperties().getLength());
        assertEquals(100, downloadLength);
    }

    @Test
    public void testAppendBlobUploadFromStreamTest() throws URISyntaxException,
            StorageException, IOException {
        final String appendBlobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testAppendBlob");
        final CloudAppendBlob appendBlobRef = this.container
                .getAppendBlobReference(appendBlobName);

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper
                .getRandomDataStream(length);
        appendBlobRef.upload(srcStream, length);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        appendBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream,
                new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = BlobTestHelper.getRandomDataStream(length);
        appendBlobRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        appendBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream,
                new ByteArrayInputStream(dstStream.toByteArray()));
    }

    @Test
    public void testBlobUploadWithoutMD5Validation() throws URISyntaxException,
            StorageException, IOException {
        final String appendBlobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testAppendBlob");
        final CloudAppendBlob appendBlobRef = this.container
                .getAppendBlobReference(appendBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper
                .getRandomDataStream(length);
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(false);
        options.setStoreBlobContentMD5(false);

        appendBlobRef.upload(srcStream, length, null, options, null);
        appendBlobRef.downloadAttributes();
        appendBlobRef.getProperties().setContentMD5("MDAwMDAwMDA=");
        appendBlobRef.uploadProperties(null, options, null);

        try {
            appendBlobRef.download(new ByteArrayOutputStream(), null, options,
                    null);
            fail();
        } catch (StorageException ex) {
            assertEquals(306, ex.getHttpStatusCode());
            assertEquals("InvalidMd5", ex.getErrorCode());
        }

        options.setDisableContentMD5Validation(true);
        appendBlobRef
                .download(new ByteArrayOutputStream(), null, options, null);
    }

    @Test
    public void testBlobEmptyHeaderSigningTest() throws URISyntaxException,
            StorageException, IOException {
        final String appendBlobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testAppendBlob");
        final CloudAppendBlob appendBlobRef = this.container
                .getAppendBlobReference(appendBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper
                .getRandomDataStream(length);

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

                    @Override
                    public void eventOccurred(SendingRequestEvent eventArg) {
                        HttpURLConnection connection = (HttpURLConnection) eventArg
                                .getConnectionObject();
                        connection.setRequestProperty("x-ms-foo", "");
                    }
                });

        appendBlobRef.upload(srcStream, length, null, null, context);
        appendBlobRef
                .download(new ByteArrayOutputStream(), null, null, context);
    }

    @Test
    public void testAppendBlobDownloadRangeTest() throws URISyntaxException,
            StorageException, IOException {
        byte[] buffer = BlobTestHelper.getRandomBuffer(2 * 1024);

        CloudAppendBlob blob = this.container.getAppendBlobReference("blob1");
        ByteArrayInputStream wholeBlob = new ByteArrayInputStream(buffer);
        BlobRequestOptions opt = new BlobRequestOptions();
        opt.setStoreBlobContentMD5(false);
        blob.upload(wholeBlob, 2 * 1024, null, opt, null);

        ByteArrayOutputStream blobStream = new ByteArrayOutputStream();
        try {
            blob.downloadRange(0, new Long(0), blobStream);
        } catch (IndexOutOfBoundsException ex) {

        }

        blob.downloadRange(0, new Long(1024), blobStream);
        assertEquals(blobStream.size(), 1024);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(
                blobStream.toByteArray()), wholeBlob, 0, 0, 1024, 2 * 1024);

        CloudAppendBlob blob2 = this.container.getAppendBlobReference("blob1");
        try {
            blob.downloadRange(1024, new Long(0), blobStream);
        } catch (IndexOutOfBoundsException ex) {

        }

        ByteArrayOutputStream blobStream2 = new ByteArrayOutputStream();
        blob2.downloadRange(1024, new Long(1024), blobStream2);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(
                blobStream2.toByteArray()), wholeBlob, 1024, 1024, 1024,
                2 * 1024);

        BlobTestHelper.assertAreEqual(blob, blob2);
    }

    public void testCloudAppendBlobDownloadToByteArray()
            throws URISyntaxException, StorageException, IOException {
        CloudAppendBlob blob = this.container.getAppendBlobReference("blob1");
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 0);
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 1 * 512);
        BlobTestHelper.doDownloadTest(blob, 2 * 512, 4 * 512, 1 * 512);
        BlobTestHelper
                .doDownloadTest(blob, 5 * 1024 * 1024, 5 * 1024 * 1024, 0);
        BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 6 * 1024 * 1024,
                512);
    }

    @Test
    public void testCloudAppendBlobDownloadRangeToByteArray()
            throws URISyntaxException, StorageException, IOException {
        CloudAppendBlob blob = this.container
                .getAppendBlobReference(BlobTestHelper
                        .generateRandomBlobNameWithPrefix("downloadrange"));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024,
                8 * 1024 * 1024, 1 * 1024 * 1024, new Long(1 * 1024 * 1024),
                new Long(5 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024,
                8 * 1024 * 1024, 2 * 1024 * 1024, new Long(2 * 1024 * 1024),
                new Long(6 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024,
                8 * 1024 * 1024, 1 * 1024 * 1024, new Long(4 * 1024 * 1024),
                new Long(4 * 1024 * 1024));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                0, new Long(1 * 512), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                1 * 512, new Long(0), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                1 * 512, new Long(1 * 512), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                1 * 512, new Long(0), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                2 * 512, new Long(1 * 512), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512,
                2 * 512, new Long(1 * 512), new Long(2 * 512));

        // Edge cases
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 1023,
                new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0,
                new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0,
                new Long(0), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0,
                new Long(512), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 512,
                new Long(1023), new Long(1));
    }

    @Test
    public void testCloudAppendBlobDownloadRangeToByteArrayNegativeTest()
            throws URISyntaxException, StorageException, IOException {
        CloudAppendBlob blob = this.container
                .getAppendBlobReference(BlobTestHelper
                        .generateRandomBlobNameWithPrefix("downloadrangenegative"));
        BlobTestHelper.doDownloadRangeToByteArrayNegativeTests(blob);
    }

    @Test
    public void testCloudAppendBlobUploadFromStreamWithAccessCondition()
            throws URISyntaxException, StorageException, IOException {
        CloudAppendBlob blob1 = this.container.getAppendBlobReference("blob1");
        AccessCondition accessCondition = AccessCondition
                .generateIfNoneMatchCondition("\"*\"");
        final int length = 6 * 512;
        ByteArrayInputStream srcStream = BlobTestHelper
                .getRandomDataStream(length);
        blob1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        blob1.createOrReplace();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob1
                .getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED,
                    ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfMatchCondition(blob1
                .getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        CloudAppendBlob blob2 = this.container.getAppendBlobReference("blob2");
        blob2.createOrReplace();
        accessCondition = AccessCondition.generateIfMatchCondition(blob1
                .getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED,
                    ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob2
                .getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);
    }

    @Test
    public void testAppendBlobNamePlusEncodingTest() throws StorageException,
            URISyntaxException, IOException, InterruptedException {
        final int length = 1 * 1024;

        final CloudAppendBlob originalBlob = (CloudAppendBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.APPEND_BLOB, "a+b.txt", length, null);
        final CloudAppendBlob copyBlob = this.container.getAppendBlobReference(originalBlob.getName() + "copyed");

        copyBlob.startCopy(originalBlob);
        BlobTestHelper.waitForCopy(copyBlob);
        copyBlob.downloadAttributes();
    }

    @Test
    public void testAppendBlobInputStream() throws URISyntaxException,
            StorageException, IOException {
        final int blobLength = 16 * 1024;
        final Random randGenerator = new Random();
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blobRef = this.container
                .getAppendBlobReference(blobName);

        final byte[] buff = new byte[blobLength];
        randGenerator.nextBytes(buff);
        buff[0] = -1;
        buff[1] = -128;
        final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);

        final BlobRequestOptions options = new BlobRequestOptions();
        final OperationContext operationContext = new OperationContext();
        options.setTimeoutIntervalInMs(90000);
        options.setRetryPolicyFactory(new RetryNoRetry());
        blobRef.upload(sourceStream, blobLength, null, options,
                operationContext);

        BlobInputStream blobStream = blobRef.openInputStream();

        for (int i = 0; i < blobLength; i++) {
            int data = blobStream.read();
            assertTrue(data >= 0);
            assertEquals(buff[i], (byte) data);
        }

        assertEquals(-1, blobStream.read());

        blobRef.delete();
    }

    @Test
    public void testAppendBlobUploadNegativeLength() throws URISyntaxException,
            StorageException, IOException {
        final int blobLength = 16 * 1024;

        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blobRef = this.container
                .getAppendBlobReference(blobName);

        final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength);
        ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);
        
        blobRef.upload(sourceStream, -1);
        
        assertTrue(blobRef.exists());
        assertEquals(blobRef.getProperties().getLength(), blobLength);
    }

    @Test
    public void testAppendBlobMaxSizeCondition() throws URISyntaxException,
            StorageException, IOException {
        final int blobLength = 16 * 1024;

        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blobRef = this.container
                .getAppendBlobReference(blobName);

        final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength);
        ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);
        
        // Max length position failure
        AccessCondition cond = new AccessCondition();
        cond.setIfMaxSizeLessThanOrEqual(blobLength - 1L);
        
        try {
            blobRef.upload(sourceStream, blobLength, cond, null, null);
            fail("Expected IOException for exceeding the max size");
        } catch (IOException ex) {
            assertEquals(SR.INVALID_BLOCK_SIZE, ex.getMessage());
        }
    }

    @Test
    public void testAppendBlobWriteStreamConditionalRetry() throws URISyntaxException,
            StorageException, IOException {
        final int blobLength = 16 * 1024;

        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blobRef = this.container
                .getAppendBlobReference(blobName);

        final byte[] buff = BlobTestHelper.getRandomBuffer(blobLength);

        final BlobRequestOptions options = new BlobRequestOptions();
        options.setAbsorbConditionalErrorsOnRetry(true);
        
        // Append position failure
        OperationContext ctx = new OperationContext();
        ctx.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() {

            int count = 0;
            
            @Override
            public void eventOccurred(ResponseReceivedEvent eventArg) {
                // This is the first try at appending. This error code will cause it to retry.
                // On the retry we set the error to append pos which should be ignored as the
                // absorb conditional errors on retry flag is set to true.
                if (count == 1) {
                    eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR);
                } else if (count == 2) {
                    eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_PRECON_FAILED);
                    eventArg.getRequestResult().setStatusMessage(StorageErrorCodeStrings.INVALID_APPEND_POSITION);
                }
                count++;
            }
        });
        
        ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);
        blobRef.upload(sourceStream, blobLength, null, options, ctx);
        
        // Max length position failure
        AccessCondition cond = new AccessCondition();
        cond.setIfMaxSizeLessThanOrEqual((long)blobLength);
        
        ctx = new OperationContext();
        ctx.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() {

            int count = 0;
            
            @Override
            public void eventOccurred(ResponseReceivedEvent eventArg) {
                // This is the first try at appending. This error code will cause it to retry.
                // On the retry we set the error to max size which should be ignored as the
                // absorb conditional errors on retry flag is set to true.
                if (count == 1) {
                    eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR);
                } else if (count == 2) {
                    eventArg.getRequestResult().setStatusCode(HttpURLConnection.HTTP_PRECON_FAILED);
                    eventArg.getRequestResult().setStatusMessage(StorageErrorCodeStrings.INVALID_MAX_BLOB_SIZE_CONDITION);
                }
                count++;
            }
        });
        
        sourceStream = new ByteArrayInputStream(buff);
        blobRef.upload(sourceStream, blobLength, cond, options, ctx);
    }

    @Test
    public void testUploadFromByteArray() throws Exception {
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blob = this.container
                .getAppendBlobReference(blobName);

        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512);
    }

    @Test
    public void testUploadDownloadFromFile() throws IOException,
            StorageException, URISyntaxException {
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blob = this.container
                .getAppendBlobReference(blobName);

        this.doUploadDownloadFileTest(blob, 512);
        this.doUploadDownloadFileTest(blob, 4096);
        this.doUploadDownloadFileTest(blob, 5 * 1024 * 1024);
        this.doUploadDownloadFileTest(blob, 11 * 1024 * 1024);
    }

    @Test
    public void testAppendBlobCopyTest() throws URISyntaxException,
            StorageException, InterruptedException, IOException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);

        CloudAppendBlob source = this.container
                .getAppendBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudAppendBlob copy = this.container.getAppendBlobReference("copy");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(source));
        BlobTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), copy.getCopyState()
                .getSource().getPath());
        assertEquals(buffer.length, copy.getCopyState().getTotalBytes()
                .intValue());
        assertEquals(buffer.length, copy.getCopyState().getBytesCopied()
                .intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime()
                .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        try {
            copy.abortCopy(copy.getCopyState().getCopyId());
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_CONFLICT,
                    ex.getHttpStatusCode());
        }

        source.downloadAttributes();
        assertNotNull(copy.getProperties().getEtag());
        assertFalse(source.getProperties().getEtag()
                .equals(copy.getProperties().getEtag()));
        assertTrue(copy.getProperties().getLastModified()
                .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(
                copyStream.toByteArray()));

        copy.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copy.getMetadata().get("Test"));

        copy.delete();
    }

    @Test
    public void testAppendBlobCopyWithMetadataOverride()
            throws URISyntaxException, StorageException, IOException, InterruptedException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        CloudAppendBlob source = this.container
                .getAppendBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);

        source.upload(stream, buffer.length);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudAppendBlob copy = this.container.getAppendBlobReference("copy");
        copy.getMetadata().put("Test2", "value2");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(source));
        BlobTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), copy.getCopyState()
                .getSource().getPath());
        assertEquals(buffer.length, copy.getCopyState().getTotalBytes()
                .intValue());
        assertEquals(buffer.length, copy.getCopyState().getBytesCopied()
                .intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime()
                .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(
                copyStream.toByteArray()));

        copy.downloadAttributes();
        source.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value2", copy.getMetadata().get("Test2"));
        assertFalse(copy.getMetadata().containsKey("Test"));

        copy.delete();
    }

    @Test
    public void testAppendBlobCopyFromSnapshot() throws StorageException,
            IOException, URISyntaxException, InterruptedException {
        CloudAppendBlob source = this.container
                .getAppendBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);

        source.upload(stream, buffer.length);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudAppendBlob snapshot = (CloudAppendBlob) source.createSnapshot();

        // Modify source
        byte[] buffer2 = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream2 = new ByteArrayInputStream(buffer2);
        source.getMetadata().put("Test", "newvalue");
        source.uploadMetadata();
        source.getProperties().setContentMD5(null);
        source.upload(stream2, buffer.length);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        source.download(outputStream);

        ByteArrayOutputStream snapshotStream = new ByteArrayOutputStream();
        snapshot.download(snapshotStream);
        BlobTestHelper.assertStreamsAreEqual(stream2, new ByteArrayInputStream(
                outputStream.toByteArray()));
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(
                snapshotStream.toByteArray()));

        source.downloadAttributes();
        snapshot.downloadAttributes();
        assertFalse(source.getMetadata().get("Test")
                .equals(snapshot.getMetadata().get("Test")));

        CloudAppendBlob copy = this.container.getAppendBlobReference("copy");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot));
        BlobTestHelper.waitForCopy(copy);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(
                copyStream.toByteArray()));
        assertEquals(copyId, copy.getProperties().getCopyState().getCopyId());

        copy.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = snapshot.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copy.getMetadata().get("Test"));

        copy.delete();
    }

    private void doUploadFromByteArrayTest(CloudAppendBlob blob,
            int bufferSize, int bufferOffset, int count) throws Exception {
        byte[] buffer = BlobTestHelper.getRandomBuffer(bufferSize);
        byte[] downloadedBuffer = new byte[bufferSize];

        blob.uploadFromByteArray(buffer, bufferOffset, count);
        blob.downloadToByteArray(downloadedBuffer, 0);

        int i = 0;
        for (; i < count; i++) {
            assertEquals(buffer[i + bufferOffset], downloadedBuffer[i]);
        }

        for (; i < downloadedBuffer.length; i++) {
            assertEquals(0, downloadedBuffer[i]);
        }
    }

    private void doUploadDownloadFileTest(CloudAppendBlob blob, int fileSize)
            throws IOException, StorageException {
        File sourceFile = File.createTempFile("sourceFile", ".tmp");
        File destinationFile = new File(sourceFile.getParentFile(),
                "destinationFile.tmp");

        try {

            byte[] buffer = BlobTestHelper.getRandomBuffer(fileSize);
            FileOutputStream fos = new FileOutputStream(sourceFile);
            fos.write(buffer);
            fos.close();
            blob.uploadFromFile(sourceFile.getAbsolutePath());

            blob.downloadToFile(destinationFile.getAbsolutePath());
            assertTrue("Destination file does not exist.",
                    destinationFile.exists());
            assertEquals("Destination file does not match input file.",
                    fileSize, destinationFile.length());
            FileInputStream fis = new FileInputStream(destinationFile);

            byte[] readBuffer = new byte[fileSize];
            fis.read(readBuffer);
            fis.close();

            for (int i = 0; i < fileSize; i++) {
                assertEquals("File contents do not match.", buffer[i],
                        readBuffer[i]);
            }
        } finally {
            if (sourceFile.exists()) {
                sourceFile.delete();
            }

            if (destinationFile.exists()) {
                destinationFile.delete();
            }
        }
    }

    @Test
    public void testUploadDownloadBlobProperties() throws URISyntaxException,
            StorageException, IOException {
        final int length = 512;

        // do this to make sure the set MD5 can be compared without an exception
        // being thrown
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(true);

        // with explicit upload/download of properties
        String appendBlobName1 = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlockBlob");
        CloudAppendBlob appendBlobRef1 = this.container
                .getAppendBlobReference(appendBlobName1);

        appendBlobRef1.upload(BlobTestHelper.getRandomDataStream(length),
                length);

        // this is not set by upload (it is for page blob!), so set this
        // manually
        appendBlobRef1.getProperties().setLength(length);

        BlobTestHelper.setBlobProperties(appendBlobRef1);
        BlobProperties props1 = appendBlobRef1.getProperties();
        appendBlobRef1.uploadProperties();

        appendBlobRef1.downloadAttributes(null, options, null);
        BlobProperties props2 = appendBlobRef1.getProperties();

        BlobTestHelper.assertAreEqual(props1, props2);

        // by uploading/downloading the blob
        appendBlobName1 = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlockBlob");
        appendBlobRef1 = this.container.getAppendBlobReference(appendBlobName1);

        BlobTestHelper.setBlobProperties(appendBlobRef1);
        props1 = appendBlobRef1.getProperties();

        appendBlobRef1.upload(BlobTestHelper.getRandomDataStream(length),
                length);

        // this is not set by upload (it is for page blob!), so set this
        // manually
        appendBlobRef1.getProperties().setLength(length);

        appendBlobRef1.download(new ByteArrayOutputStream(), null, options,
                null);
        props2 = appendBlobRef1.getProperties();

        BlobTestHelper.assertAreEqual(props1, props2);
    }

    @Test
    public void testOpenOutputStream() throws URISyntaxException,
            StorageException, IOException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512);

        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        final CloudAppendBlob blobRef = this.container
                .getAppendBlobReference(blobName);
        blobRef.createOrReplace();

        BlobOutputStream blobOutputStream = blobRef.openWriteNew();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);

        blobOutputStream = blobRef.openWriteNew();
        inputStream = new ByteArrayInputStream(buffer);
        blobOutputStream.write(inputStream, 512);

        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        blobOutputStream.write(inputStream, 3 * 512);

        blobOutputStream.close();

        byte[] result = new byte[blobLengthToUse];
        blobRef.downloadToByteArray(result, 0);

        int i = 0;
        for (; i < 4 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (; i < 8 * 512; i++) {
            assertEquals(0, result[i]);
        }
    }

    @Test
    public void testOpenOutputStreamNoArgs() throws URISyntaxException,
            StorageException {
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob appendBlob = this.container
                .getAppendBlobReference(blobName);

        try {
            appendBlob.openWriteExisting();
        } catch (StorageException ex) {
            assertEquals("The specified blob does not exist.", ex.getMessage());
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND,
                    ex.getHttpStatusCode());
        }

        appendBlob.openWriteNew();
        appendBlob.openWriteExisting();

        CloudAppendBlob appendBlob2 = this.container
                .getAppendBlobReference(blobName);
        appendBlob2.downloadAttributes();
        assertEquals(0, appendBlob2.getProperties().getLength());
        assertEquals(BlobType.APPEND_BLOB, appendBlob2.getProperties().getBlobType());
    }

    @Test
    public void testOpenOutputStreamWithConditions() throws StorageException, IOException, URISyntaxException
    {
        int blobSize = 1024;
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob blob = container.getAppendBlobReference(blobName);
       
        BlobOutputStream str = blob.openWriteNew();
        byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize);
        str.write(buffer);
        str.close();
        
        // Succeeded max size condition
        AccessCondition accessCondition = new AccessCondition();
        accessCondition.setIfMaxSizeLessThanOrEqual(1024*2+1L);
        str = blob.openWriteExisting(accessCondition, null, null);
        str.write(buffer);
        str.close();
        
        // Succeeded append position condition
        accessCondition = new AccessCondition();
        accessCondition.setIfAppendPositionEqual(1024*2L);
        str = blob.openWriteExisting(accessCondition, null, null);
        str.write(buffer);
        str.close();
        
        // Failed max size condition
        accessCondition = new AccessCondition();
        accessCondition.setIfMaxSizeLessThanOrEqual(1024 - 1L);
        try {
            str = blob.openWriteExisting(accessCondition, null, null);
            str.write(buffer);
            str.close();
            fail("Expected a condition failure.");
        } catch (IOException ex) {
            assertEquals(SR.INVALID_BLOCK_SIZE, ex.getMessage());
        }
        
        // Failed append position condition
        accessCondition = new AccessCondition();
        accessCondition.setIfAppendPositionEqual(1024 - 1L);
        try {
            str = blob.openWriteExisting(accessCondition, null, null);
            str.write(buffer);
            str.close();
            fail("Expected a condition failure.");
        } catch (IOException ex) {
            StorageException internalException = (StorageException)ex.getCause();
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, internalException.getHttpStatusCode());
            assertEquals("The append position condition specified was not met.", internalException.getMessage());
            assertEquals("AppendPositionConditionNotMet", internalException.getErrorCode());
        }
    }

    @Test
    public void testOpenOutputStreamMultiWriterFail() throws StorageException,
            IOException, URISyntaxException {
        int blobSize = 1024;
        byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize);
        
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob blob = container.getAppendBlobReference(blobName);
        blob.createOrReplace();
        final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer);
        blob.appendBlock(sourceStream, blobSize);
        
        // Create two streams to the same blob
        BlobOutputStream str = blob.openWriteExisting();
        BlobOutputStream str2 = blob.openWriteExisting();
        
        // These will write to an internal buffer and not throw as they do
        // not yet make a service call
        str.write(buffer);
        str2.write(buffer);
        
        // Append the data from the first stream (service call happens)
        str.close();
        
        // Failed append position condition
        try {
            // Append the data from the second stream which expects append
            // position to be 1024 from the intial download properties when
            // the stream was opened
            str2.close();
            fail("Expected a condition failure.");
        } catch (IOException ex) {
            StorageException internalException = (StorageException) ex
                    .getCause();
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED,
                    internalException.getHttpStatusCode());
            assertEquals(
                    "The append position condition specified was not met.",
                    internalException.getMessage());
            assertEquals("AppendPositionConditionNotMet",
                    internalException.getErrorCode());
        }
    }

    @Test
    public void testAppendBlockFromStream() throws StorageException, IOException, URISyntaxException
    {
        int blobSize = 2 * 1024;
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob blob = container.getAppendBlobReference(blobName);
        blob.createOrReplace();

        // Append a block
        byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize);
        ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer);
        long pos = blob.appendBlock(sourceStream, -1);
        assertEquals(0, pos);

        // Download and verify equality
        byte[] resultBuffer = new byte[blobSize];
        blob.downloadToByteArray(resultBuffer, 0);
        for (int i = 0; i < blob.getProperties().getLength(); i++) {
            assertEquals(buffer[i], resultBuffer[i]);
        }
        
        // Append another block to check the position is updated correctly
        sourceStream = new ByteArrayInputStream(buffer);
        pos = blob.appendBlock(sourceStream, -1);
        assertEquals(blobSize, pos);
    }

    @Test
    public void testAppendBlockFromStreamWithConditions() throws StorageException, IOException, URISyntaxException
    {
        int blobSize = 1024;
        String blobName = BlobTestHelper
                .generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob blob = this.container.getAppendBlobReference(blobName);
        blob.createOrReplace();

        byte[] buffer = BlobTestHelper.getRandomBuffer(blobSize);
        ByteArrayInputStream sourceStream = new ByteArrayInputStream(buffer);
        blob.appendBlock(sourceStream, -1); 
        
        // Succeeded max size condition
        AccessCondition accessCondition = new AccessCondition();
        accessCondition.setIfMaxSizeLessThanOrEqual(1024*2+1L);
        sourceStream = new ByteArrayInputStream(buffer);
        blob.appendBlock(sourceStream, -1, accessCondition, null, null);
        
        // Succeeded append position condition
        accessCondition = new AccessCondition();
        accessCondition.setIfAppendPositionEqual(1024*2L);
        sourceStream = new ByteArrayInputStream(buffer);
        blob.appendBlock(sourceStream, -1, accessCondition, null, null);
        
        // Failed max size condition
        accessCondition = new AccessCondition();
        accessCondition.setIfMaxSizeLessThanOrEqual(1024 - 1L);
        try {
            sourceStream = new ByteArrayInputStream(buffer);
            blob.appendBlock(sourceStream, -1, accessCondition, null, null);
            fail("Expected a condition failure.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED,
                    ex.getHttpStatusCode());
            assertEquals("The max blob size condition specified was not met.", ex.getMessage());
            assertEquals("MaxBlobSizeConditionNotMet", ex.getErrorCode());
        }
        
        // Failed append position condition
        accessCondition = new AccessCondition();
        accessCondition.setIfAppendPositionEqual(1024 - 1L);
        try {
            sourceStream = new ByteArrayInputStream(buffer);
            blob.appendBlock(sourceStream, -1, accessCondition, null, null);
            fail("Expected a condition failure.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED,
                    ex.getHttpStatusCode());
            assertEquals("The append position condition specified was not met.", ex.getMessage());
            assertEquals("AppendPositionConditionNotMet", ex.getErrorCode());
        }
    }

    @Test
    public void testAppendBlobUploadFromStream() throws StorageException, IOException, URISyntaxException
    {
        byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024);

        CloudAppendBlob blob = this.container.getAppendBlobReference("blob1");

        blob.upload(new ByteArrayInputStream(buffer), buffer.length);
        blob.downloadAttributes();
        assertEquals(6 * 1024 * 1024, blob.getProperties().getLength());

        blob.upload(new ByteArrayInputStream(buffer), buffer.length);
        blob.downloadAttributes();
        assertEquals(6 * 1024 * 1024, blob.getProperties().getLength());

        blob.upload(new ByteArrayInputStream(buffer), buffer.length, null /* accessCondition */, null /* options */,
                null /* operationContext */);
        blob.downloadAttributes();
        assertEquals(6 * 1024 * 1024, blob.getProperties().getLength());
    }

    @Test
    public void testAppendBlobAppendFromStream() throws StorageException, IOException, URISyntaxException
    {
        // Every time append a buffer that is bigger than a single block.
        byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024);

        CloudAppendBlob blob = this.container.getAppendBlobReference("blob1");
        blob.createOrReplace();

        blob.append(new ByteArrayInputStream(buffer), buffer.length);
        blob.downloadAttributes();
        assertEquals(6 * 1024 * 1024, blob.getProperties().getLength());

        blob.append(new ByteArrayInputStream(buffer), buffer.length);
        blob.downloadAttributes();
        assertEquals(12 * 1024 * 1024, blob.getProperties().getLength());

        blob.append(new ByteArrayInputStream(buffer), buffer.length, null /* accessCondition */, null /* options */, 
                null /* operationContext */);
        blob.downloadAttributes();
        assertEquals(18 * 1024 * 1024, blob.getProperties().getLength());
    }

    @Test
    public void testAppendBlobAppendFromStreamWithLength() throws StorageException, IOException, URISyntaxException
    {
        // Every time append a buffer that is bigger than a single block.
        byte[] buffer = BlobTestHelper.getRandomBuffer(6 * 1024 * 1024);

        CloudAppendBlob blob = this.container.getAppendBlobReference("blob1");
        blob.createOrReplace();

        blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024);
        blob.downloadAttributes();
        assertEquals(5 * 1024 * 1024, blob.getProperties().getLength());

        blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024);
        blob.downloadAttributes();
        assertEquals(10 * 1024 * 1024, blob.getProperties().getLength());

        blob.append(new ByteArrayInputStream(buffer), 5 * 1024 * 1024, null /* accessCondition */, null /* options */, 
                null /* operationContext */);
        blob.downloadAttributes();
        assertEquals(15 * 1024 * 1024, blob.getProperties().getLength());
    }
} public class CloudBlobClientTests {
    /**
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListContainers() throws StorageException, URISyntaxException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();
        ArrayList<String> containerList = new ArrayList<String>();
        String prefix = UUID.randomUUID().toString();
        for (int i = 0; i < 30; i++) {
            containerList.add(prefix + i);
            bClient.getContainerReference(prefix + i).create();
        }

        int count = 0;
        for (final CloudBlobContainer container : bClient.listContainers(prefix)) {
            assertEquals(CloudBlobContainer.class, container.getClass());
            count++;
        }
        assertEquals(30, count);

        ResultContinuation token = null;
        do {

            ResultSegment<CloudBlobContainer> segment = bClient.listContainersSegmented(prefix,
                    ContainerListingDetails.ALL, 15, token, null, null);

            for (final CloudBlobContainer container : segment.getResults()) {
                container.downloadAttributes();
                assertEquals(CloudBlobContainer.class, container.getClass());
                containerList.remove(container.getName());
            }

            token = segment.getContinuationToken();
        } while (token != null);

        assertEquals(0, containerList.size());
    }

    /**
     * Try to list the containers to ensure maxResults validation is working.
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListContainersMaxResultsValidation()
            throws StorageException, URISyntaxException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();
        String prefix = UUID.randomUUID().toString();

        // Validation should cause each of these to fail.
        for(int i = 0; i >= -2; i--) {
            try {
                bClient.listContainersSegmented(
                        prefix, ContainerListingDetails.ALL, i, null, null, null);
                fail();
            }
            catch (IllegalArgumentException e) {
                assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1)
                        .equals(e.getMessage()));
            }
        }
        assertNotNull(bClient.listContainersSegmented("thereshouldntbeanycontainersswiththisprefix"));
    }

    /**
     * Fetch result segments and ensure pageSize is null when unspecified and will cap at 5000.
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListContainersResultSegment()
            throws StorageException, URISyntaxException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();

        ResultSegment<CloudBlobContainer> segment1 = bClient.listContainersSegmented();
        assertNotNull(segment1);
        assertNull(segment1.getPageSize());

        ResultSegment<CloudBlobContainer> segment2 = bClient.listContainersSegmented(null,
                ContainerListingDetails.ALL, 9001, null, null, null);
        assertNotNull(segment2);
        assertNotNull(segment2.getPageSize());
        assertEquals(5000, segment2.getPageSize().intValue());
    }

    /**
     * List containers and fetch attributes with public access
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCreateContainerWithPublicAccess() throws StorageException, URISyntaxException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();
        BlobContainerPublicAccessType[] accessTypes = { BlobContainerPublicAccessType.CONTAINER, 
                BlobContainerPublicAccessType.OFF, BlobContainerPublicAccessType.BLOB };
        for(BlobContainerPublicAccessType accessType : accessTypes)
        {
            String name = UUID.randomUUID().toString();
            CloudBlobContainer container = bClient.getContainerReference(name);
            assertNull(container.properties.getPublicAccess());
            container.create(accessType, null, null);
            assertEquals(accessType, container.properties.getPublicAccess());

            CloudBlobContainer containerRef = bClient.getContainerReference(name);
            assertNull(containerRef.properties.getPublicAccess());
            BlobContainerPermissions permissions = containerRef.downloadPermissions();
            assertEquals(accessType, containerRef.properties.getPublicAccess());
            assertEquals(accessType, permissions.getPublicAccess());
            
            CloudBlobContainer containerRef2 = bClient.getContainerReference(name);
            assertEquals(null, containerRef2.properties.getPublicAccess());
            containerRef2.exists();
            assertEquals(accessType, containerRef2.properties.getPublicAccess());

            String name2 = UUID.randomUUID().toString();
            CloudBlobContainer container2 = bClient.getContainerReference(name2);
            assertNull(container2.properties.getPublicAccess());
            container2.create();
            assertEquals(BlobContainerPublicAccessType.OFF, container2.properties.getPublicAccess());

            BlobContainerPermissions permissions2 = new BlobContainerPermissions();
            permissions2.setPublicAccess(accessType);
            container2.uploadPermissions(permissions);
            assertEquals(accessType, container2.properties.getPublicAccess());
            
            CloudBlobContainer containerRef3 = bClient.getContainerReference(name);
            containerRef3.downloadAttributes();
            assertEquals(accessType, containerRef3.properties.getPublicAccess());

            String name3 = UUID.randomUUID().toString();
            CloudBlobContainer container3 = bClient.getContainerReference(name3);
            container3.create(null, null, null);
            assertEquals(BlobContainerPublicAccessType.OFF, container3.properties.getPublicAccess());

            container.delete();
            container2.delete();
            container3.delete();
        }
    }

    /**
     * List containers and fetch attributes with public access
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListContainersAndFetchAttributesWithPublicAccess() throws StorageException, URISyntaxException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();
        String name = UUID.randomUUID().toString();
        CloudBlobContainer container = bClient.getContainerReference(name);
        container.create();
        BlobContainerPublicAccessType[] accessTypes = {BlobContainerPublicAccessType.BLOB,
                BlobContainerPublicAccessType.CONTAINER, BlobContainerPublicAccessType.OFF};
        BlobContainerPermissions permissions = new BlobContainerPermissions();
        for (BlobContainerPublicAccessType accessType : accessTypes) {
            permissions.setPublicAccess(accessType);
            container.uploadPermissions(permissions);
            assertEquals(accessType, container.properties.getPublicAccess());

            CloudBlobContainer container2 = bClient.getContainerReference(name);
            assertNull(container2.properties.getPublicAccess());
            container2.downloadAttributes();
            assertEquals(accessType, container2.properties.getPublicAccess());
            
            CloudBlobContainer container3 = bClient.getContainerReference(name);
            assertNull(container3.properties.getPublicAccess());
            assertEquals(accessType, container3.downloadPermissions().getPublicAccess());

            Iterator<CloudBlobContainer> results = bClient.listContainers(name, ContainerListingDetails.NONE, null, null).iterator();
            assertTrue(results.hasNext());
            assertEquals(accessType, results.next().properties.getPublicAccess());
            assertFalse(results.hasNext());
        }

        container.delete();
    }

    @Test
    @Category({ CloudTests.class })
    public void testGetServiceStats() throws StorageException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();
        bClient.getDefaultRequestOptions().setLocationMode(LocationMode.SECONDARY_ONLY);
        BlobTestHelper.verifyServiceStats(bClient.getServiceStats());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testSingleBlobPutThresholdInBytes() throws URISyntaxException, StorageException, IOException {
        CloudBlobClient bClient = BlobTestHelper.createCloudBlobClient();

        try {
            bClient.getDefaultRequestOptions().setSingleBlobPutThresholdInBytes(
                    BlobConstants.MAX_SINGLE_UPLOAD_BLOB_SIZE_IN_BYTES + 1);
            fail("Cannot set upload blob threshold above 64 MB");
        }
        catch (IllegalArgumentException e) {
            assertEquals(
                    "The argument is out of range. Argument name: singleBlobPutThresholdInBytes, Value passed: 67108865.",
                    e.getMessage());
        }

        try {
            bClient.getDefaultRequestOptions().setSingleBlobPutThresholdInBytes(Constants.MB - 1);
            fail("Cannot set upload blob threshold below 1 MB");
        }
        catch (IllegalArgumentException e) {
            assertEquals(
                    "The argument is out of range. Argument name: singleBlobPutThresholdInBytes, Value passed: 1048575.",
                    e.getMessage());
        }

        int maxSize = 2 * Constants.MB;

        bClient.getDefaultRequestOptions().setSingleBlobPutThresholdInBytes(maxSize);

        final ArrayList<Boolean> callList = new ArrayList<Boolean>();
        OperationContext sendingRequestEventContext = new OperationContext();
        sendingRequestEventContext.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                assertEquals(eventArg.getRequestResult(), eventArg.getOpContext().getLastResult());
                callList.add(true);
            }
        });

        assertEquals(0, callList.size());

        CloudBlobContainer container = null;
        try {
            container = bClient.getContainerReference(BlobTestHelper.generateRandomContainerName());
            container.createIfNotExists();
            CloudBlockBlob blob = container.getBlockBlobReference(BlobTestHelper
                    .generateRandomBlobNameWithPrefix("uploadThreshold"));

            // this should make a single call as it is less than the max
            blob.upload(BlobTestHelper.getRandomDataStream(maxSize - 1), maxSize - 1, null, null,
                    sendingRequestEventContext);

            assertEquals(1, callList.size());

            // this should make one call as it is equal to the max
            blob.upload(BlobTestHelper.getRandomDataStream(maxSize), maxSize, null, null, sendingRequestEventContext);

            assertEquals(2, callList.size());

            // this should make two calls as it is greater than the max
            blob.upload(BlobTestHelper.getRandomDataStream(maxSize + 1), maxSize + 1, null, null,
                    sendingRequestEventContext);

            assertEquals(4, callList.size());
        }
        finally {
            container.deleteIfExists();
        }
    }
} @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());
        }
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class CloudBlobDirectoryTests {

    private static final String[] delimiters = { "$", ":", "@", "-", "%", "/", "|", "//", "%%", " " };

    @Test
    public void testGetDirectoryAbsoluteUriAppended() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testGetDirectoryAbsoluteUriAppended(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testGetDirectoryAbsoluteUriAppended(String delimiter, CloudBlobContainer container)
            throws URISyntaxException {
        CloudBlobDirectory dir = container.getDirectoryReference(container.getUri().toString());
        assertEquals(PathUtility.appendPathToSingleUri(container.getUri(), container.getUri().toString() + delimiter),
                dir.getUri());

        dir = container.getDirectoryReference(container.getUri().toString() + "/" + "TopDir1" + delimiter);
        assertEquals(
                PathUtility.appendPathToSingleUri(container.getUri(), container.getUri().toString() + "/" + "TopDir1"
                        + delimiter), dir.getUri());
    }

    @Test
    public void testFlatListingWithContainer() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testFlatListingWithContainer(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testFlatListingWithContainer(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        // check output of list blobs with TopDir1 and hierarchical
        Iterable<ListBlobItem> list1 = container.listBlobs("TopDir1" + delimiter, false,
                EnumSet.noneOf(BlobListingDetails.class), null, null);

        Iterator<ListBlobItem> iter = list1.iterator();
        ArrayList<ListBlobItem> simpleList1 = new ArrayList<ListBlobItem>();
        while (iter.hasNext()) {
            simpleList1.add(iter.next());
        }

        assertEquals(3, simpleList1.size());

        ListBlobItem get11 = simpleList1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "Blob1", null, null), get11.getUri());

        ListBlobItem get12 = simpleList1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = simpleList1.get(2);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());

        // check output of list blobs with TopDir1 + delimiter + MidDir1
        Iterable<ListBlobItem> list2 = container.listBlobs("TopDir1" + delimiter + "MidDir1", true,
                EnumSet.noneOf(BlobListingDetails.class), null, null);

        iter = list2.iterator();
        ArrayList<ListBlobItem> simpleList2 = new ArrayList<ListBlobItem>();
        while (iter.hasNext()) {
            simpleList2.add(iter.next());
        }

        assertEquals(2, simpleList2.size());

        ListBlobItem get21 = simpleList2.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter + "EndBlob1", null,
                null), get21.getUri());

        ListBlobItem get22 = simpleList2.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir2" + delimiter + "EndBlob2", null,
                null), get22.getUri());

        // check output of list blobs with TopDir1 + delimiter + MidDir1 + delimiter
        Iterable<ListBlobItem> list3 = container.listBlobs("TopDir1" + delimiter + "MidDir1" + delimiter, false,
                EnumSet.noneOf(BlobListingDetails.class), null, null);

        iter = list3.iterator();
        ArrayList<ListBlobItem> simpleList3 = new ArrayList<ListBlobItem>();
        while (iter.hasNext()) {
            simpleList3.add(iter.next());
        }

        assertEquals(2, simpleList3.size());

        ListBlobItem get31 = simpleList3.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, null, null),
                get31.getUri());

        ListBlobItem get32 = simpleList3.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir2" + delimiter, null, null),
                get32.getUri());

        // check output of list blobs by listing using midDir
        CloudBlobDirectory midDir2 = (CloudBlobDirectory) get13;

        Iterable<ListBlobItem> list4 = midDir2.listBlobs(null, true, EnumSet.noneOf(BlobListingDetails.class), null,
                null);

        iter = list4.iterator();
        ArrayList<ListBlobItem> simpleList4 = new ArrayList<ListBlobItem>();
        while (iter.hasNext()) {
            simpleList4.add(iter.next());
        }

        assertEquals(2, simpleList4.size());

        ListBlobItem get41 = simpleList4.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir1" + delimiter + "EndBlob1", null,
                null), get41.getUri());

        ListBlobItem get42 = simpleList4.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir2" + delimiter + "EndBlob2", null,
                null), get42.getUri());
    }

    @Test
    public void testListingWithDirectory() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testListingWithDirectory(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testListingWithDirectory(String delimiter, CloudBlobContainer container) throws URISyntaxException,
            StorageException {
        // check output of list blobs with TopDir1 with prefix
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1" + delimiter + "MidDir1" + delimiter);

        Iterable<ListBlobItem> results = directory.listBlobs();

        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        Iterator<ListBlobItem> iter = results.iterator();
        while (iter.hasNext()) {
            list1.add(iter.next());
        }

        assertEquals(2, list1.size());

        ListBlobItem get21 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, null, null),
                get21.getUri());

        ListBlobItem get22 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir2" + delimiter, null, null),
                get22.getUri());
    }

    @Test
    public void testListingWithDirectorySegmented() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testListingWithDirectorySegmented(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testListingWithDirectorySegmented(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        ResultContinuation token = null;
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1");
        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        do {
            ResultSegment<ListBlobItem> result1 = directory.listBlobsSegmented("Mid");
            token = result1.getContinuationToken();
            list1.addAll(result1.getResults());
        } while (token != null);

        assertEquals(2, list1.size());

        ListBlobItem get21 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get21.getUri());

        ListBlobItem get22 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get22.getUri());
    }

    @Test
    public void testPrefixListingWithDirectory() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testPrefixListingWithDirectory(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testPrefixListingWithDirectory(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        // check output of list blobs with TopDir1 with prefix
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1");

        Iterable<ListBlobItem> results = directory.listBlobs("Mid");

        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        Iterator<ListBlobItem> iter = results.iterator();
        while (iter.hasNext()) {
            list1.add(iter.next());
        }

        assertEquals(2, list1.size());

        ListBlobItem get12 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());
    }

    @Test
    public void testPrefixListingWithDirectorySegmented() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testPrefixListingWithDirectorySegmented(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testPrefixListingWithDirectorySegmented(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        ResultContinuation token = null;
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1");
        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        do {
            ResultSegment<ListBlobItem> result1 = directory.listBlobsSegmented("Mid");
            token = result1.getContinuationToken();
            list1.addAll(result1.getResults());
        } while (token != null);

        assertEquals(2, list1.size());

        ListBlobItem get12 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());
    }

    @Test
    public void testFlatListingWithDirectory() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testFlatListingWithDirectory(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testFlatListingWithDirectory(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        // check output of list blobs with TopDir1 and flat listing
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1");

        Iterable<ListBlobItem> results = directory.listBlobs(null, false, EnumSet.noneOf(BlobListingDetails.class),
                null, null);

        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        Iterator<ListBlobItem> iter = results.iterator();
        while (iter.hasNext()) {
            list1.add(iter.next());
        }

        assertEquals(3, list1.size());

        ListBlobItem get11 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "Blob1", null, null), get11.getUri());

        ListBlobItem get12 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = list1.get(2);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());

        CloudBlobDirectory midDir2 = (CloudBlobDirectory) get13;

        // check output of list blobs with TopDir1 + delimiter + MidDir1 and hierarchical listing
        results = midDir2.listBlobs(null, true, EnumSet.noneOf(BlobListingDetails.class), null, null);

        ArrayList<ListBlobItem> list2 = new ArrayList<ListBlobItem>();
        iter = results.iterator();
        while (iter.hasNext()) {
            list2.add(iter.next());
        }

        assertEquals(2, list2.size());

        ListBlobItem get21 = list2.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir1" + delimiter + "EndBlob1", null,
                null), get21.getUri());

        ListBlobItem get22 = list2.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir2" + delimiter + "EndBlob2", null,
                null), get22.getUri());
    }

    @Test
    public void testFlatListingWithDirectorySegmented() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testFlatListingWithDirectorySegmented(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testFlatListingWithDirectorySegmented(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        // check output of list blobs with TopDir1 and flat listing
        ResultContinuation token = null;
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1");
        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        do {
            ResultSegment<ListBlobItem> result1 = directory.listBlobsSegmented(null, false,
                    EnumSet.noneOf(BlobListingDetails.class), null, token, null, null);
            token = result1.getContinuationToken();
            list1.addAll(result1.getResults());
        } while (token != null);

        assertEquals(3, list1.size());

        ListBlobItem get11 = list1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "Blob1", null, null), get11.getUri());

        ListBlobItem get12 = list1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = list1.get(2);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());

        CloudBlobDirectory midDir2 = (CloudBlobDirectory) get13;

        // check output of list blobs with TopDir1 + delimiter + MidDir1
        ArrayList<ListBlobItem> list2 = new ArrayList<ListBlobItem>();
        do {
            ResultSegment<ListBlobItem> result1 = midDir2.listBlobsSegmented(null, true,
                    EnumSet.noneOf(BlobListingDetails.class), null, token, null, null);
            token = result1.getContinuationToken();
            list2.addAll(result1.getResults());
        } while (token != null);

        assertEquals(2, list2.size());

        ListBlobItem get21 = list2.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir1" + delimiter + "EndBlob1", null,
                null), get21.getUri());

        ListBlobItem get22 = list2.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter + "EndDir2" + delimiter + "EndBlob2", null,
                null), get22.getUri());
    }

    @Test
    public void testGetParent() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testGetParent(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testGetParent(String delimiter, CloudBlobContainer container) throws URISyntaxException,
            StorageException {
        CloudPageBlob blob = container.getPageBlobReference("Dir1" + delimiter + "Blob1");
        blob.create(0);
        try {
            assertTrue(blob.exists());
            assertEquals(blob.getName(), "Dir1" + delimiter + "Blob1");
            CloudBlobDirectory parent = blob.getParent();
            assertEquals("Dir1" + delimiter, parent.getPrefix());
        }
        finally {
            blob.deleteIfExists();
        }
    }

    @Test
    public void testGetReferences() throws URISyntaxException, StorageException, IOException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testGetReferences(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testGetReferences(String delimiter, CloudBlobContainer container) throws URISyntaxException,
            StorageException, IOException {
        CloudBlobDirectory dir1 = container.getDirectoryReference("Dir1");

        // get references to blobs, block blobs and directories from the directory and create them
        CloudPageBlob pageBlob = dir1.getPageBlobReference("PageBlob");
        pageBlob.create(0);
        assertTrue(pageBlob.exists());
        assertEquals(dir1.getContainer().getName(), pageBlob.getContainer().getName());
        assertEquals(dir1.getServiceClient().getEndpoint().toString(), pageBlob.getServiceClient().getEndpoint()
                .toString());
        assertEquals("Dir1" + delimiter + "PageBlob", pageBlob.getName());
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/Dir1" + delimiter + "PageBlob", null, null), pageBlob.getUri());
        CloudBlobDirectory pageParent = pageBlob.getParent();
        assertEquals("Dir1" + delimiter, pageParent.getPrefix());

        CloudBlockBlob blockBlob = dir1.getBlockBlobReference("BlockBlob");
        blockBlob.upload(BlobTestHelper.getRandomDataStream(128), 128);
        assertTrue(blockBlob.exists());
        assertEquals(dir1.getContainer().getName(), pageBlob.getContainer().getName());
        assertEquals(dir1.getServiceClient().getEndpoint().toString(), blockBlob.getServiceClient().getEndpoint()
                .toString());
        assertEquals("Dir1" + delimiter + "BlockBlob", blockBlob.getName());
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/Dir1" + delimiter + "BlockBlob", null, null), blockBlob.getUri());
        CloudBlobDirectory blockParent = blockBlob.getParent();
        assertEquals("Dir1" + delimiter, blockParent.getPrefix());

        CloudBlobDirectory subDirectory = dir1.getDirectoryReference("SubDirectory");
        assertEquals(dir1.getContainer().getName(), subDirectory.getContainer().getName());
        assertEquals(dir1.getServiceClient().getEndpoint().toString(), subDirectory.getServiceClient().getEndpoint()
                .toString());
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/Dir1" + delimiter + "SubDirectory" + delimiter, null, null), subDirectory.getUri());
        CloudBlobDirectory subDirectoryParent = subDirectory.getParent();
        assertEquals("Dir1" + delimiter, subDirectoryParent.getPrefix());

        // create snapshots for page and block blobs and then get references to them using the directory
        CloudPageBlob pageSnapshot = (CloudPageBlob) pageBlob.createSnapshot();
        CloudPageBlob pageSnapshotDir1 = dir1.getPageBlobReference("PageBlob", pageSnapshot.getSnapshotID());
        assertEquals(pageSnapshot.getName(), pageSnapshotDir1.getName());
        assertEquals(dir1.getContainer().getName(), pageSnapshotDir1.getContainer().getName());
        assertEquals(dir1.getServiceClient().getEndpoint().toString(), pageSnapshotDir1.getServiceClient()
                .getEndpoint().toString());
        assertEquals(pageSnapshot.getUri().toString(), pageSnapshotDir1.getUri().toString());
        assertEquals(pageSnapshot.getParent().getPrefix(), pageSnapshotDir1.getParent().getPrefix());

        CloudBlockBlob blockSnapshot = (CloudBlockBlob) blockBlob.createSnapshot();
        CloudBlockBlob blockSnapshotDir1 = dir1.getBlockBlobReference("BlockBlob", blockSnapshot.getSnapshotID());
        assertEquals(blockSnapshot.getName(), blockSnapshotDir1.getName());
        assertEquals(dir1.getContainer().getName(), blockSnapshotDir1.getContainer().getName());
        assertEquals(dir1.getServiceClient().getEndpoint().toString(), blockSnapshotDir1.getServiceClient()
                .getEndpoint().toString());
        assertEquals(blockSnapshot.getUri().toString(), blockSnapshotDir1.getUri().toString());
        assertEquals(blockSnapshot.getParent().getPrefix(), blockSnapshotDir1.getParent().getPrefix());
    }

    @Test
    public void testGetSubdirectoryAndTraverseBackToParent() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testGetSubdirectoryAndTraverseBackToParent(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testGetSubdirectoryAndTraverseBackToParent(String delimiter, CloudBlobContainer container)
            throws URISyntaxException, StorageException {
        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1" + delimiter);
        CloudBlobDirectory subDirectory = directory.getDirectoryReference("MidDir1" + delimiter);
        CloudBlobDirectory parent = subDirectory.getParent();
        assertEquals(parent.getPrefix(), directory.getPrefix());
        assertEquals(parent.getUri(), directory.getUri());
    }

    @Test
    public void testGetParentOnRoot() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testGetParentOnRoot(delimiters[i], createAndPopulateContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testGetParentOnRoot(String delimiter, CloudBlobContainer container) throws URISyntaxException,
            StorageException {
        // get container as parent
        CloudBlobDirectory topDir = container.getDirectoryReference("TopDir1" + delimiter);
        CloudBlobDirectory root = topDir.getParent();
        assertEquals("", root.getPrefix());
        assertEquals(container.getUri().toString(), root.getUri().toString());

        // make sure the parent of the container dir is null
        CloudBlobDirectory empty = root.getParent();
        assertNull(empty);

        // from container, get directory reference to container
        root = container.getDirectoryReference("");
        assertEquals("", root.getPrefix());
        assertEquals(container.getUri().toString(), root.getUri().toString());

        Iterable<ListBlobItem> results = root.listBlobs();

        ArrayList<ListBlobItem> list1 = new ArrayList<ListBlobItem>();
        Iterator<ListBlobItem> iter = results.iterator();
        while (iter.hasNext()) {
            list1.add(iter.next());
        }
        assertEquals(2, list1.size());

        // make sure the parent of the container dir is null
        empty = root.getParent();
        assertNull(empty);
    }

    @Test
    public void testHierarchicalTraversal() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testHierarchicalTraversal(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testHierarchicalTraversal(String delimiter, CloudBlobContainer container) throws StorageException,
            URISyntaxException {
        // Traverse hierarchically starting with length 1
        CloudBlobDirectory directory1 = container.getDirectoryReference("Dir1" + delimiter);

        CloudBlobDirectory subdir1 = directory1.getDirectoryReference("Dir2");
        CloudBlobDirectory parent1 = subdir1.getParent();
        assertEquals(parent1.getPrefix(), directory1.getPrefix());

        CloudBlobDirectory subdir2 = subdir1.getDirectoryReference("Dir3");
        CloudBlobDirectory parent2 = subdir2.getParent();
        assertEquals(parent2.getPrefix(), subdir1.getPrefix());

        CloudBlobDirectory subdir3 = subdir2.getDirectoryReference("Dir4");
        CloudBlobDirectory parent3 = subdir3.getParent();
        assertEquals(parent3.getPrefix(), subdir2.getPrefix());

        CloudBlobDirectory subdir4 = subdir3.getDirectoryReference("Dir5");
        CloudBlobDirectory parent4 = subdir4.getParent();
        assertEquals(parent4.getPrefix(), subdir3.getPrefix());
    }

    @Test
    public void testParentValidate() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testParentValidate(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testParentValidate(String delimiter, CloudBlobContainer container) throws StorageException,
            URISyntaxException {
        CloudBlockBlob blob = container.getBlockBlobReference("TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1"
                + delimiter + "EndBlob1");
        CloudBlobDirectory directory = blob.getParent();
        assertEquals("TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, directory.getPrefix());
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, null, null),
                directory.getUri());
    }

    public void testValidateInRootContainer() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            testValidateInRootContainer(delimiters[i]);
        }
    }

    private void testValidateInRootContainer(String delimiter) throws URISyntaxException, StorageException {
        CloudBlobClient client = BlobTestHelper.createCloudBlobClient();
        client.setDirectoryDelimiter(delimiter);
        CloudBlobContainer container = client.getContainerReference("$root");

        CloudPageBlob pageBlob = container.getPageBlobReference("Dir1" + delimiter + "Blob1");
        if (delimiter.equals("/")) {
            try {
                pageBlob.create(0);
                pageBlob.deleteIfExists();
                fail("Try to create a CloudBlobDirectory/blob which has a slash in its name in the root container");
            }
            catch (StorageException e) {
                assertEquals("InvalidUri", e.getErrorCode());
                assertTrue(e.getExtendedErrorInformation().getErrorMessage()
                        .startsWith("The requested URI does not represent any resource on the server."));
            }
        }

        else {
            CloudPageBlob blob = container.getPageBlobReference("TopDir1" + delimiter + "MidDir1" + delimiter
                    + "EndDir1" + delimiter + "EndBlob1");
            CloudBlobDirectory directory = blob.getParent();
            assertEquals("TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, directory.getPrefix());
            assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                    .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter, null, null),
                    directory.getUri());
            assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                    .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter + "EndDir1" + delimiter + "EndBlob1",
                    null, null), blob.getUri());

            CloudBlobDirectory directory1 = container.getDirectoryReference("TopDir1" + delimiter);
            CloudBlobDirectory root = directory1.getParent();
            assertEquals("", root.getPrefix());
            assertEquals(container.getUri(), root.getUri());

            CloudBlobDirectory subdir1 = directory1.getDirectoryReference("MidDir" + delimiter);
            CloudBlobDirectory parent1 = subdir1.getParent();
            assertEquals(directory1.getPrefix(), parent1.getPrefix());
            assertEquals(directory1.getUri(), parent1.getUri());

            CloudBlobDirectory subdir2 = subdir1.getDirectoryReference("EndDir" + delimiter);
            CloudBlobDirectory parent2 = subdir2.getParent();
            assertEquals(subdir1.getPrefix(), parent2.getPrefix());
            assertEquals(subdir1.getUri(), parent2.getUri());
        }
    }

    @Test
    public void testDelimitersInARow() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testDelimitersInARow(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testDelimitersInARow(String delimiter, CloudBlobContainer container) throws URISyntaxException,
            StorageException {
        CloudPageBlob blob = container.getPageBlobReference(delimiter + delimiter + delimiter + "Blob1");

        // Traverse from leaf to root
        CloudBlobDirectory directory1 = blob.getParent();
        assertEquals(delimiter + delimiter + delimiter, directory1.getPrefix());

        CloudBlobDirectory directory2 = directory1.getParent();
        assertEquals(delimiter + delimiter, directory2.getPrefix());

        CloudBlobDirectory directory3 = directory2.getParent();
        assertEquals(delimiter, directory3.getPrefix());

        // Traverse from root to leaf
        CloudBlobDirectory directory4 = container.getDirectoryReference(delimiter);
        CloudBlobDirectory directory5 = directory4.getDirectoryReference(delimiter);
        assertEquals(delimiter + delimiter, directory5.getPrefix());

        CloudBlobDirectory directory6 = directory5.getDirectoryReference(delimiter);
        assertEquals(delimiter + delimiter + delimiter, directory6.getPrefix());

        CloudPageBlob blob2 = directory6.getPageBlobReference("Blob1");
        assertEquals(delimiter + delimiter + delimiter + "Blob1", blob2.getName());
        assertEquals(blob.getUri(), blob2.getUri());
    }

    @Test
    public void testMultipleDelimiters() throws URISyntaxException, StorageException {
        for (int i = 0; i < delimiters.length; i++) {
            CloudBlobContainer container = null;
            try {
                container = createContainer(delimiters[i]);
                testMultipleDelimiters(delimiters[i], createContainer(delimiters[i]));
            }
            finally {
                container.deleteIfExists();
            }
        }
    }

    private void testMultipleDelimiters(String delimiter, CloudBlobContainer container) throws StorageException,
            URISyntaxException {
        populateContainer(container, delimiter);
        populateContainer(container, "*");
        populateContainer(container, "#");

        Iterable<ListBlobItem> list1 = container.listBlobs("TopDir1" + delimiter, false,
                EnumSet.noneOf(BlobListingDetails.class), null, null);

        Iterator<ListBlobItem> iter = list1.iterator();
        ArrayList<ListBlobItem> simpleList1 = new ArrayList<ListBlobItem>();
        while (iter.hasNext()) {
            simpleList1.add(iter.next());
        }

        assertEquals(3, simpleList1.size());

        ListBlobItem get11 = simpleList1.get(0);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "Blob1", null, null), get11.getUri());

        ListBlobItem get12 = simpleList1.get(1);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir1" + delimiter, null, null), get12.getUri());

        ListBlobItem get13 = simpleList1.get(2);
        assertEquals(new URI(container.getUri().getScheme(), container.getUri().getAuthority(), container.getUri()
                .getPath() + "/TopDir1" + delimiter + "MidDir2" + delimiter, null, null), get13.getUri());

        CloudBlobDirectory directory = container.getDirectoryReference("TopDir1" + delimiter);
        CloudBlobDirectory subDirectory = directory.getDirectoryReference("MidDir1" + delimiter);
        CloudBlobDirectory parent = subDirectory.getParent();
        assertEquals(parent.getPrefix(), directory.getPrefix());
        assertEquals(parent.getUri(), directory.getUri());
    }

    private static CloudBlobContainer createAndPopulateContainer(String delimiter) throws URISyntaxException,
            StorageException {
        CloudBlobContainer container = createContainer(delimiter);
        populateContainer(container, delimiter);
        return container;
    }

    private static void populateContainer(CloudBlobContainer container, String delimiter) throws URISyntaxException,
            StorageException {
        for (int i = 1; i < 3; i++) {
            for (int j = 1; j < 3; j++) {
                for (int k = 1; k < 3; k++) {
                    String directory = "TopDir" + i + delimiter + "MidDir" + j + delimiter + "EndDir" + k + delimiter
                            + "EndBlob" + k;
                    CloudPageBlob blob1 = container.getPageBlobReference(directory);
                    blob1.create(0);
                }
            }

            CloudPageBlob blob2 = container.getPageBlobReference("TopDir" + i + delimiter + "Blob" + i);
            blob2.create(0);
        }
    }

    private static CloudBlobContainer createContainer(String delimiter) throws URISyntaxException, StorageException {
        CloudBlobClient client = BlobTestHelper.createCloudBlobClient();
        client.setDirectoryDelimiter(delimiter);
        String name = BlobTestHelper.generateRandomContainerName();
        CloudBlobContainer container = client.getContainerReference(name);
        container.create();
        return container;
    }
} @Category({ CloudTests.class, DevFabricTests.class, DevStoreTests.class })
//@Ignore
/* These test only works on accounts with server-side encryption enabled. */
public class CloudBlobServerEncryptionTests {

    private CloudBlobContainer container;
    private CloudBlockBlob blob;
    private boolean requestFound;

    @Before
    public void blobEncryptionTestMethodSetup() throws URISyntaxException, StorageException, IOException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();
        this.blob = this.container.getBlockBlobReference(BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob"));
        this.blob.uploadText("test");
    }

    @After
    public void blobEncryptionTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    @Test
    public void testBlobAttributesEncryption() throws URISyntaxException, StorageException, IOException {
        this.blob.downloadAttributes();
        assertTrue(this.blob.getProperties().isServerEncrypted());

        CloudBlockBlob testBlob = this.container.getBlockBlobReference(this.blob.getName());
        testBlob.downloadText();
        assertTrue(testBlob.getProperties().isServerEncrypted());
    }

    @Test
    public void testListBlobsEncryption() throws URISyntaxException, StorageException, IOException {
        boolean blobFound = false;

        for (ListBlobItem b : this.container.listBlobs()) {
            CloudBlob blob = (CloudBlob) b;
            assertTrue(blob.getProperties().isServerEncrypted());

            blobFound = true;
        }

        assertTrue(blobFound);
    }

    @Test
    public void testBlobEncryption() throws URISyntaxException, StorageException, IOException {
        this.requestFound = false;
        
        OperationContext ctxt = new OperationContext();
        ctxt.getRequestCompletedEventHandler().addListener(new StorageEvent<RequestCompletedEvent>() {
            @Override
            public void eventOccurred(RequestCompletedEvent eventArg) {
                assertTrue(eventArg.getRequestResult().isRequestServiceEncrypted());
                CloudBlobServerEncryptionTests.this.requestFound = true;
            }
        });

        this.blob.uploadText("test", null, null, null, ctxt);
        assertTrue(this.requestFound);
    }
} @Category({ CloudTests.class })
public class CloudBlockBlobTests {

    protected CloudBlobContainer container;

    @Before
    public void cloudBlockBlobTestMethodSetUp() throws URISyntaxException, StorageException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();
    }

    @After
    public void cloudBlockBlobTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    /**
     * Create a block blob.
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlockBlobCreate() throws StorageException, URISyntaxException, IOException {
        final CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));

        assertFalse(blob.exists());

        // Create
        blob.uploadText("text");
        assertTrue(blob.exists());

        // Create again (should succeed)
        blob.uploadText("text");
        assertTrue(blob.exists());

        // Create again, specifying not to if it already exists
        // This should fail
        // Add 15 min to account for clock skew
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        cal.add(Calendar.MINUTE, 15);
        AccessCondition condition = new AccessCondition();
        condition.setIfModifiedSinceDate(cal.getTime());

        try {
            blob.uploadText("text", null, condition, null, null);
            fail("Create should fail do to access condition.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", ex.getMessage());
            assertEquals("ConditionNotMet", ex.getErrorCode());
        }
    }

    /**
     * Delete a block blob.
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlockBlobDelete() throws StorageException, URISyntaxException, IOException {
        final CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));

        assertFalse(blob.exists());

        // create
        blob.uploadText("text");
        assertTrue(blob.exists());

        // delete
        blob.delete();
        assertFalse(blob.exists());

        // delete again, should fail as it doesn't exist
        try {
            blob.delete();
            fail("Delete should fail as blob does not exist.");
        } catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
            assertEquals("The specified blob does not exist.", ex.getMessage());
            assertEquals("BlobNotFound", ex.getErrorCode());
        }
    }

    /**
     * Test blob name validation.
     */
    @Test
    public void testCloudBlobNameValidation()
    {
        NameValidator.validateBlobName("alpha");
        NameValidator.validateBlobName("4lphanum3r1c");
        NameValidator.validateBlobName("CAPSLOCK");
        NameValidator.validateBlobName("white space");
        NameValidator.validateBlobName("0th3r(h@racter$");
        NameValidator.validateBlobName(new String(new char[253]).replace("\0", "a/a"));

        invalidBlobTestHelper("", "No empty strings.", "Invalid blob name. The name may not be null, empty, or whitespace only.");
        invalidBlobTestHelper(null, "No null strings.", "Invalid blob name. The name may not be null, empty, or whitespace only.");
        invalidBlobTestHelper(new String(new char[1025]).replace("\0", "n"), "Maximum 1024 characters.", "Invalid blob name length. The name must be between 1 and 1024 characters long.");
        invalidBlobTestHelper(new String(new char[254]).replace("\0", "a/a"), "Maximum 254 path segments.", "The count of URL path segments (strings between '/' characters) as part of the blob name cannot exceed 254.");
    }

    private void invalidBlobTestHelper(String blobName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateBlobName(blobName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    @Test
    public void testBlobUriOnlyConstructors() throws URISyntaxException, StorageException, InvalidKeyException {
        URI blobURI = new URI(container.getUri().toString() + "/anonblob");
        CloudBlockBlob blob = new CloudBlockBlob(blobURI);
        assertEquals("anonblob", blob.getName());
        assertNotNull(blob.getServiceClient());
        assertEquals(StorageCredentialsAnonymous.class, blob.getServiceClient().getCredentials().getClass());

        blob = container.getBlockBlobReference("anonblob");

        String sas = blob.generateSharedAccessSignature(null, "dummyPolicy");
        blobURI = new URI(container.getUri().toString() + "/anonblob?" + sas);
        blob = new CloudBlockBlob(blobURI);
        assertEquals("anonblob", blob.getName());
        assertNotNull(blob.getServiceClient());
        assertEquals(StorageCredentialsSharedAccessSignature.class, blob.getServiceClient().getCredentials().getClass());
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyBlockBlobSasToSasTest() throws InvalidKeyException, URISyntaxException, StorageException,
    IOException, InterruptedException {
        this.doCloudBlockBlobCopy(true, true);
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyBlockBlobToSasTest() throws InvalidKeyException, URISyntaxException, StorageException,
    IOException, InterruptedException {
        this.doCloudBlockBlobCopy(false, true);
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyBlockBlobSasTest() throws InvalidKeyException, URISyntaxException, StorageException,
    IOException, InterruptedException {
        this.doCloudBlockBlobCopy(true, false);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testCopyBlockBlobTest() throws InvalidKeyException, URISyntaxException, StorageException, IOException,
    InterruptedException {
        this.doCloudBlockBlobCopy(false, false);
    }

    @Test
    public void testCopyWithChineseChars() throws StorageException, IOException, URISyntaxException {
        String data = "sample data chinese chars 阿䶵";
        CloudBlockBlob copySource = container.getBlockBlobReference("sourcechinescharsblob阿䶵.txt");
        copySource.uploadText(data);

        assertEquals(this.container.getUri() + "/sourcechinescharsblob阿䶵.txt", copySource.getUri().toString());
        assertEquals(this.container.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt",
                copySource.getUri().toASCIIString());

        CloudBlockBlob copyDestination = container.getBlockBlobReference("destchinesecharsblob阿䶵.txt");

        assertEquals(this.container.getUri() + "/destchinesecharsblob阿䶵.txt", copyDestination.getUri().toString());
        assertEquals(this.container.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt",
                copyDestination.getUri().toASCIIString());

        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {
            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject();

                // Test the copy destination request url
                assertEquals(CloudBlockBlobTests.this.container.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt",
                        con.getURL().toString());

                // Test the copy source request property
                assertEquals(CloudBlockBlobTests.this.container.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt",
                        con.getRequestProperty("x-ms-copy-source"));
            }
        });

        copyDestination.startCopy(copySource.getUri(), null, null, null, ctx);
        copyDestination.startCopy(copySource, null, null, null, ctx);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testCopyBlockBlobWithMetadataOverride() throws URISyntaxException, StorageException, IOException,
    InterruptedException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        String data = "String data";
        CloudBlockBlob source = this.container.getBlockBlobReference("source");

        BlobTestHelper.setBlobProperties(source);

        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudBlockBlob copy = this.container.getBlockBlobReference("copy");
        copy.getMetadata().put("Test2", "value2");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(source));
        BlobTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), copy.getCopyState().getSource().getPath());
        assertEquals(data.length(), copy.getCopyState().getTotalBytes().intValue());
        assertEquals(data.length(), copy.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        String copyData = copy.downloadText(Constants.UTF8_CHARSET, null, null, null);
        assertEquals(data, copyData);

        copy.downloadAttributes();
        source.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value2", copy.getMetadata().get("Test2"));
        assertFalse(copy.getMetadata().containsKey("Test"));

        copy.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testCopyBlockBlobFromSnapshot() throws StorageException, IOException, URISyntaxException,
    InterruptedException {
        CloudBlockBlob source = this.container.getBlockBlobReference("source");
        String data = "String data";

        BlobTestHelper.setBlobProperties(source);

        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudBlockBlob snapshot = (CloudBlockBlob) source.createSnapshot();

        //Modify source
        String newData = "Hello";
        source.getMetadata().put("Test", "newvalue");
        source.uploadMetadata();
        source.getProperties().setContentMD5(null);
        source.uploadText(newData, Constants.UTF8_CHARSET, null, null, null);

        assertEquals(newData, source.downloadText(Constants.UTF8_CHARSET, null, null, null));
        assertEquals(data, snapshot.downloadText(Constants.UTF8_CHARSET, null, null, null));

        source.downloadAttributes();
        snapshot.downloadAttributes();
        assertFalse(source.getMetadata().get("Test").equals(snapshot.getMetadata().get("Test")));

        CloudBlockBlob copy = this.container.getBlockBlobReference("copy");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot));
        BlobTestHelper.waitForCopy(copy);

        copy.downloadAttributes();

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(data, copy.downloadText(Constants.UTF8_CHARSET, null, null, null));
        assertEquals(copyId, copy.getProperties().getCopyState().getCopyId());

        copy.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = snapshot.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copy.getMetadata().get("Test"));

        copy.delete();
    }

    /**
     * Start copying a blob and then abort
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCopyFromBlobAbortTest() throws StorageException, URISyntaxException, IOException {
        final int length = 128;
        CloudBlockBlob originalBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.BLOCK_BLOB, "originalBlob", length, null);
        CloudBlockBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed");
        copyBlob.startCopy(originalBlob);

        try {
            copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId());
        }
        catch (StorageException e) {
            if (!e.getErrorCode().contains("NoPendingCopyOperation")) {
                throw e;
            }
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyFileSas()
            throws InvalidKeyException, URISyntaxException, StorageException, IOException, InterruptedException {
        // Create source on server.
        final CloudFileShare share = FileTestHelper.getRandomShareReference();
        try {
            share.create();
            final CloudFile source = share.getRootDirectoryReference().getFileReference("source");

            final String data = "String data";
            source.getMetadata().put("Test", "value");
            source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

            Calendar cal = Calendar.getInstance(Utility.UTC_ZONE);
            cal.add(Calendar.MINUTE, 5);

            // Source SAS must have read permissions
            SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
            policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ));
            policy.setSharedAccessExpiryTime(cal.getTime());

            String sasToken = source.generateSharedAccessSignature(policy, null, null);

            // Get destination reference
            final CloudBlockBlob destination = this.container.getBlockBlobReference("destination");

            // Start copy and wait for completion
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            String copyId = destination.startCopy(new CloudFile(credentials.transformUri(source.getUri())));
            BlobTestHelper.waitForCopy(destination);
            destination.downloadAttributes();
            assertNotNull(destination.getProperties().getEtag());

            // Check original file references for equality
            assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus());
            assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(),
                    destination.getCopyState().getSource().getPath());
            assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue());
            assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue());
            assertEquals(copyId, destination.getProperties().getCopyState().getCopyId());

            // Attempt to abort the completed copy operation.
            try {
                destination.abortCopy(destination.getCopyState().getCopyId());
                fail();
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
            }

            String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null);
            assertEquals(data, copyData);

            source.downloadAttributes();
            BlobProperties prop1 = destination.getProperties();
            FileProperties prop2 = source.getProperties();

            assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
            assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
            assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
            assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
            assertEquals(prop1.getContentType(), prop2.getContentType());

            assertEquals("value", destination.getMetadata().get("Test"));
            assertEquals(1, destination.getMetadata().size());
        }
        finally {
            share.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testCopyFileWithMetadataOverride()
            throws URISyntaxException, StorageException, IOException, InterruptedException, InvalidKeyException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        String data = "String data";

        final CloudFileShare share = FileTestHelper.getRandomShareReference();
        try {
            share.create();
            final CloudFile source = share.getRootDirectoryReference().getFileReference("source");
            FileTestHelper.setFileProperties(source);

            // do this to make sure the set MD5 can be compared, otherwise when the dummy value
            // doesn't match the actual MD5 an exception would be thrown
            BlobRequestOptions options = new BlobRequestOptions();
            options.setDisableContentMD5Validation(true);

            source.getMetadata().put("Test", "value");
            source.uploadText(data);

            calendar.add(Calendar.MINUTE, 5);

            // Source SAS must have read permissions
            SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
            policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ));
            policy.setSharedAccessExpiryTime(calendar.getTime());

            String sasToken = source.generateSharedAccessSignature(policy, null, null);

            // Get source BlockBlob reference
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            CloudBlockBlob destination = this.container.getBlockBlobReference("copy");

            destination.getMetadata().put("Test2", "value2");
            String copyId = destination.startCopy(
                    FileTestHelper.defiddler(new CloudFile(credentials.transformUri(source.getUri()))));
            BlobTestHelper.waitForCopy(destination);
            destination.downloadAttributes();

            assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus());
            assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(),
                    destination.getCopyState().getSource().getPath());
            assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue());
            assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue());
            assertEquals(copyId, destination.getCopyState().getCopyId());
            assertTrue(0 < destination.getCopyState().getCompletionTime().compareTo(
                    new Date(calendar.get(Calendar.MINUTE) - 6)));

            String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, options, null);
            assertEquals(data, copyData);

            source.downloadAttributes();
            BlobProperties prop1 = destination.getProperties();
            FileProperties prop2 = source.getProperties();

            assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
            assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
            assertEquals(prop1.getContentDisposition(),
                    prop2.getContentDisposition());
            assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
            assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
            assertEquals(prop1.getContentType(), prop2.getContentType());

            assertEquals("value2", destination.getMetadata().get("Test2"));
            assertFalse(destination.getMetadata().containsKey("Test"));
            assertEquals(1, destination.getMetadata().size());
        }
        finally {
            share.deleteIfExists();
        }
    }

    /**
     * Start copying a file and then abort
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InvalidKeyException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCopyFileAbort()
            throws StorageException, URISyntaxException, IOException, InvalidKeyException, InterruptedException {
        final int length = 128;
        final CloudFileShare share = FileTestHelper.getRandomShareReference();
        share.create();
        final CloudFile source = FileTestHelper.uploadNewFile(share, length, null);

        // Source SAS must have read permissions
        SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
        policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ));

        Calendar cal = Calendar.getInstance(Utility.UTC_ZONE);
        cal.add(Calendar.MINUTE, 5);
        policy.setSharedAccessExpiryTime(cal.getTime());
        String sasToken = source.generateSharedAccessSignature(policy, null, null);

        // Start copy and wait for completion
        final CloudBlockBlob destination = this.container.getBlockBlobReference(source.getName() + "copyed");
        StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
        destination.startCopy(new CloudFile(credentials.transformUri(source.getUri())));

        try {
            destination.abortCopy(destination.getProperties().getCopyState().getCopyId());
            BlobTestHelper.waitForCopy(destination);
            fail();
        }
        catch (StorageException e) {
            if (!e.getErrorCode().contains("NoPendingCopyOperation")) {
                throw e;
            }
        }
        finally {
            share.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteBlobIfExists() throws URISyntaxException, StorageException, IOException {
        final CloudBlockBlob blob1 = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("testBlob"));

        assertFalse(blob1.exists());
        assertFalse(blob1.deleteIfExists());

        blob1.uploadText("test1");
        assertTrue(blob1.exists());

        assertTrue(blob1.deleteIfExists());
        assertFalse(blob1.deleteIfExists());

        // check if second condition works in delete if exists
        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

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

        // The second delete of a blob will return a 404
        blob1.uploadText("test1");
        assertFalse(blob1.deleteIfExists(DeleteSnapshotsOption.NONE, null, null, ctx));
    }

    /**
     * Create a snapshot
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testBlobSnapshotValidationTest() throws StorageException, URISyntaxException, IOException{
        final int length = 1024;
        CloudBlockBlob blockBlobRef = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container,
                BlobType.BLOCK_BLOB, "originalBlob", length, null);
        final CloudBlob blobSnapshot = blockBlobRef.createSnapshot();

        for (ListBlobItem blob : this.container.listBlobs(null, true, EnumSet.allOf(BlobListingDetails.class), null,
                null)) {
            final ByteArrayOutputStream outStream = new ByteArrayOutputStream(length);
            ((CloudBlob) blob).download(outStream);
        }

        ByteArrayOutputStream outStream = new ByteArrayOutputStream(length);

        blobSnapshot.download(outStream);
        byte[] retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);

        // Read operation should work fine.
        blobSnapshot.downloadAttributes();

        final CloudBlockBlob blobSnapshotUsingRootUri = this.container.getBlockBlobReference(blockBlobRef.getName(),
                blobSnapshot.getSnapshotID());
        outStream = new ByteArrayOutputStream(length);

        blobSnapshotUsingRootUri.download(outStream);
        retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);
        assertEquals(blobSnapshot.getSnapshotID(), blobSnapshotUsingRootUri.getSnapshotID());

        // Expect an IllegalArgumentException from upload.
        try {
            final Random randGenerator = new Random();
            final byte[] buff = new byte[length];
            randGenerator.nextBytes(buff);
            blobSnapshot.upload(new ByteArrayInputStream(buff), -1);
            fail("Expect an IllegalArgumentException from upload");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadMetadata.
        try {
            blobSnapshot.uploadMetadata();
            fail("Expect an IllegalArgumentException from uploadMetadata");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadProperties.
        try {
            blobSnapshot.uploadProperties();
            fail("Expect an IllegalArgumentException from uploadProperties");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from createSnapshot.
        try {
            blobSnapshot.createSnapshot();
            fail("Expect an IllegalArgumentException from createSnapshot");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }
    }

    /**
     * Create a blob and try to download a range of its contents
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testBlobDownloadRangeValidationTest() throws StorageException, URISyntaxException, IOException {
        final int blockLength = 1024 * 1024;
        final int length = 5 * blockLength;

        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        ArrayList<BlockEntry> blockList = new ArrayList<BlockEntry>();
        for (int i = 1; i <= 5; i++) {
            String blockID = String.format("%08d", i);
            blockBlobRef
            .uploadBlock(blockID, BlobTestHelper.getRandomDataStream(length), blockLength, null, null, null);
            blockList.add(new BlockEntry(blockID, BlockSearchMode.LATEST));
        }

        blockBlobRef.commitBlockList(blockList);

        //Download full blob
        blockBlobRef.download(new ByteArrayOutputStream());
        assertEquals(length, blockBlobRef.getProperties().getLength());

        //Download blob range.
        ByteArrayOutputStream downloadStream = new ByteArrayOutputStream();
        blockBlobRef.downloadRange(0, (long) 100, downloadStream);
        assertEquals(length, blockBlobRef.getProperties().getLength());

        //Download block list.
        blockBlobRef.downloadBlockList();
        assertEquals(length, blockBlobRef.getProperties().getLength());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCommitBlockListContentMd5() throws URISyntaxException, StorageException, IOException {
        int length = 1024;
        byte[] buffer = BlobTestHelper.getRandomBuffer(length);
        Map<String, BlockEntry> blocks = BlobTestHelper.getBlockEntryList(3);
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("blob1");

        CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);
        for (BlockEntry block : blocks.values()) {
            blob.uploadBlock(block.getId(), new ByteArrayInputStream(buffer), length);
        }

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

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection conn = (HttpURLConnection)eventArg.getConnectionObject();
                assertNull(conn.getRequestProperty("Content-MD5"));
            }
        });

        blob.commitBlockList(blocks.values(), null, null, ctx);

        BlobRequestOptions opt = new BlobRequestOptions();
        opt.setUseTransactionalContentMD5(true);

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

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection conn = (HttpURLConnection)eventArg.getConnectionObject();
                assertNotNull(conn.getRequestProperty("Content-MD5"));
            }
        });

        blob.commitBlockList(blocks.values(), null, opt, ctx);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDownloadBlockList() throws URISyntaxException, StorageException, IOException {
        int length = 1024;
        byte[] buffer = BlobTestHelper.getRandomBuffer(length);
        Map<String, BlockEntry> blocks = BlobTestHelper.getBlockEntryList(3);
        Map<String, BlockEntry> extraBlocks = BlobTestHelper.getBlockEntryList(2);
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("blob1");

        CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);
        for (BlockEntry block : blocks.values()) {
            blob.uploadBlock(block.getId(), new ByteArrayInputStream(buffer), length);
        }
        blob.commitBlockList(blocks.values());

        for (BlockEntry block : extraBlocks.values()) {
            blob.uploadBlock(block.getId(), new ByteArrayInputStream(buffer), length);
        }

        CloudBlockBlob blob2 = this.container.getBlockBlobReference(blobName);
        blob2.downloadAttributes();
        assertEquals(1024 * blocks.size(), blob2.getProperties().getLength());

        List<BlockEntry> blockList = blob2.downloadBlockList();
        assertEquals(3, blockList.size());
        for (BlockEntry blockItem : blockList) {
            assertEquals(BlockSearchMode.COMMITTED, blockItem.getSearchMode());
            assertEquals(length, blockItem.getSize());
            assertFalse(blocks.remove(blockItem.getId()) == null);
        }
        assertEquals(0, blocks.size());

        blockList = blob2.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null);
        assertEquals(2, blockList.size());
        for (BlockEntry blockItem : blockList) {
            assertEquals(BlockSearchMode.UNCOMMITTED, blockItem.getSearchMode());
            assertEquals(length, blockItem.getSize());
            assertFalse(extraBlocks.remove(blockItem.getId()) == null);
        }
        assertEquals(0, extraBlocks.size());

        blockList = blob2.downloadBlockList(BlockListingFilter.ALL, null, null, null);
        assertEquals(5, blockList.size());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlockBlobDownloadRangeTest() throws URISyntaxException, StorageException, IOException {
        byte[] buffer = BlobTestHelper.getRandomBuffer(2 * 1024);

        CloudBlockBlob blob = this.container.getBlockBlobReference("blob1");
        ByteArrayInputStream wholeBlob = new ByteArrayInputStream(buffer);
        blob.upload(wholeBlob, -1);

        ByteArrayOutputStream blobStream = new ByteArrayOutputStream();
        try {
            blob.downloadRange(0, new Long(0), blobStream, null, null, null);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        blob.downloadRange(0, new Long(1024), blobStream);
        assertEquals(blobStream.size(), 1024);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(blobStream.toByteArray()), wholeBlob, 0,
                0, 1024, 2 * 1024);

        CloudBlockBlob blob2 = this.container.getBlockBlobReference("blob1");
        try {
            blob.downloadRange(1024, new Long(0), blobStream, null, null, null);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        ByteArrayOutputStream blobStream2 = new ByteArrayOutputStream();
        blob2.downloadRange(1024, new Long(1024), blobStream2);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(blobStream2.toByteArray()), wholeBlob,
                1024, 1024, 1024, 2 * 1024);
        blob2.downloadAttributes();
        BlobTestHelper.assertAreEqual(blob, blob2);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobUploadFromStreamTest() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        blockBlobRef.upload(srcStream, -1);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        blockBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = BlobTestHelper.getRandomDataStream(length);
        blockBlobRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        blockBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));
    }

    @Test
    public void testBlobUploadFromStreamAccessConditionTest() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);
        AccessCondition accessCondition = AccessCondition.generateIfNotModifiedSinceCondition(new Date());

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        blockBlobRef.upload(srcStream, -1, accessCondition, null, null);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        blockBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = BlobTestHelper.getRandomDataStream(length);
        blockBlobRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        blockBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobUploadFromStreamRequestOptionsTest() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef1 = this.container.getBlockBlobReference(blockBlobName1);

        final String blockBlobName2 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef2 = this.container.getBlockBlobReference(blockBlobName2);

        final int length = 2 * com.microsoft.azure.storage.Constants.MB;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);

        BlobRequestOptions options = new BlobRequestOptions();
        options.setSingleBlobPutThresholdInBytes(length / 2);
        options.setRetryPolicyFactory(RetryNoRetry.getInstance());
        OperationContext context = new OperationContext();
        blockBlobRef1.upload(srcStream, length, null /* accessCondition */, options, context);

        assertTrue(context.getRequestResults().size() >= 2);

        srcStream.reset();
        options.setSingleBlobPutThresholdInBytes(length);
        context = new OperationContext();
        blockBlobRef2.upload(srcStream, length, null /* accessCondition */, options, context);

        assertTrue(context.getRequestResults().size() <= 2);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadDownloadBlobProperties() throws URISyntaxException, StorageException, IOException {
        final int length = 0;

        // do this to make sure the set MD5 can be compared without an exception being thrown
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(true);

        // with explicit upload/download of properties
        String blockBlobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        CloudBlockBlob blockBlobRef1 = this.container.getBlockBlobReference(blockBlobName1);

        blockBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), length);

        // this is not set by upload (it is for page blob!), so set this manually
        blockBlobRef1.getProperties().setLength(length);

        BlobTestHelper.setBlobProperties(blockBlobRef1);
        BlobProperties props1 = blockBlobRef1.getProperties();
        blockBlobRef1.uploadProperties();

        blockBlobRef1.downloadAttributes(null, options, null);
        BlobProperties props2 = blockBlobRef1.getProperties();

        Assert.assertEquals(props1.getLength(), props2.getLength());
        BlobTestHelper.assertAreEqual(props1, props2);

        // by uploading/downloading the blob
        blockBlobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        blockBlobRef1 = this.container.getBlockBlobReference(blockBlobName1);

        BlobTestHelper.setBlobProperties(blockBlobRef1);
        props1 = blockBlobRef1.getProperties();

        blockBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), length);

        blockBlobRef1.download(new ByteArrayOutputStream(), null, options, null);
        props2 = blockBlobRef1.getProperties();

        BlobTestHelper.assertAreEqual(props1, props2);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobUploadWithoutMD5Validation() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(false);
        options.setStoreBlobContentMD5(false);

        blockBlobRef.upload(srcStream, -1, null, options, null);
        blockBlobRef.downloadAttributes();
        blockBlobRef.getProperties().setContentMD5("MDAwMDAwMDA=");
        blockBlobRef.uploadProperties(null, options, null);

        try {
            blockBlobRef.download(new ByteArrayOutputStream(), null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(306, ex.getHttpStatusCode());
            assertEquals("InvalidMd5", ex.getErrorCode());
        }

        options.setDisableContentMD5Validation(true);
        blockBlobRef.download(new ByteArrayOutputStream(), null, options, null);

        final CloudBlockBlob blockBlobRef2 = this.container.getBlockBlobReference(blockBlobName);
        assertNull(blockBlobRef2.getProperties().getContentMD5());

        byte[] target = new byte[4];
        blockBlobRef2.downloadRangeToByteArray(0L, 4L, target, 0);
        assertEquals("MDAwMDAwMDA=", blockBlobRef2.properties.getContentMD5());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testVerifyTransactionalMD5ValidationMissingOverallMD5() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        final int length = 2 * 1024 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        BlobRequestOptions options = new BlobRequestOptions();
        options.setSingleBlobPutThresholdInBytes(1024*1024);
        options.setDisableContentMD5Validation(true);
        options.setStoreBlobContentMD5(false);

        blockBlobRef.upload(srcStream, -1, null, options, null);

        options.setDisableContentMD5Validation(false);
        options.setStoreBlobContentMD5(true);
        options.setUseTransactionalContentMD5(true);
        final CloudBlockBlob blockBlobRef2 = this.container.getBlockBlobReference(blockBlobName);
        blockBlobRef2.downloadRange(1024, (long)1024, new ByteArrayOutputStream(), null, options, null);
        assertNull(blockBlobRef2.getProperties().getContentMD5());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlockBlobUploadContentMD5() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        int length = 16 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);

        final ArrayList<Boolean> callList = new ArrayList<Boolean>();
        OperationContext sendingRequestEventContext = new OperationContext();
        StorageEvent<SendingRequestEvent> event = new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                assertNotNull(((HttpURLConnection) eventArg.getConnectionObject())
                        .getRequestProperty(BlobConstants.BLOB_CONTENT_MD5_HEADER));
                callList.add(true);
            }
        };

        sendingRequestEventContext.getSendingRequestEventHandler().addListener(event);
        assertEquals(0, callList.size());

        // Upload with length less than single threshold. Make sure we calculate the contentMD5.
        blockBlobRef.upload(srcStream, length, null, null, sendingRequestEventContext);
        assertEquals(1, callList.size());

        sendingRequestEventContext.getSendingRequestEventHandler().removeListener(event);
        callList.clear();

        event = new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                callList.add(true);
            }
        };

        length = 33 * 1024 * 1024;
        srcStream = BlobTestHelper.getRandomDataStream(length);

        sendingRequestEventContext.getSendingRequestEventHandler().addListener(event);
        assertEquals(0, callList.size());

        // Upload with length greater than single threshold. This will do multiple block uploads.
        blockBlobRef.upload(srcStream, length, null, null, sendingRequestEventContext);
        assertTrue(callList.size() > 1);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobEmptyHeaderSigningTest() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);

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

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                connection.setRequestProperty("x-ms-foo", "");
            }
        });

        blockBlobRef.upload(srcStream, -1, null, null, context);
        blockBlobRef.download(new ByteArrayOutputStream(), null, null, context);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobDownloadToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudBlockBlob blob = this.container.getBlockBlobReference("blob1");
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 0);
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 1 * 512);
        BlobTestHelper.doDownloadTest(blob, 2 * 512, 4 * 512, 1 * 512);
        BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 5 * 1024 * 1024, 0);
        BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 6 * 1024 * 1024, 512);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobDownloadRangeToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("downloadrange"));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(1 * 1024 * 1024), new Long(5 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 2 * 1024 * 1024,
                new Long(2 * 1024 * 1024), new Long(6 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(4 * 1024 * 1024), new Long(4 * 1024 * 1024));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 0, new Long(1 * 512), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(0), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(1 * 512), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(0), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                2 * 512));

        // Edge cases
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 1023, new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(0), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(512), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 512, new Long(1023), new Long(1));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobDownloadRangeToByteArrayNegativeTest() throws URISyntaxException, StorageException,
    IOException {
        CloudBlockBlob blob = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("downloadrangenegative"));
        BlobTestHelper.doDownloadRangeToByteArrayNegativeTests(blob);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobUploadFromStreamWithAccessCondition() throws URISyntaxException, StorageException,
            IOException {
        CloudBlockBlob blob1 = this.container.getBlockBlobReference("blob1");
        AccessCondition accessCondition = AccessCondition.generateIfNoneMatchCondition("\"*\"");

        final int length = 2 * 1024 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);

        blob1.upload(srcStream, length, accessCondition, null, null);
        blob1.downloadAttributes();

        CloudBlockBlob blob2 = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB,
                "accesscond", length, null);
        blob2.downloadAttributes();

        srcStream.reset();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob1.getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfMatchCondition(blob1.getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        accessCondition = AccessCondition.generateIfMatchCondition(blob2.getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob2.getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);
    }

    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testBlobNamePlusEncodingTest() throws StorageException, URISyntaxException, IOException,
    InterruptedException {
        final int length = 1 * 1024;

        final CloudBlockBlob originalBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container,
                BlobType.BLOCK_BLOB, "a+b.txt", length, null);
        final CloudBlockBlob copyBlob = this.container.getBlockBlobReference(originalBlob.getName() + "copyed");

        copyBlob.startCopy(originalBlob);
        BlobTestHelper.waitForCopy(copyBlob);
        copyBlob.downloadAttributes();
    }

    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testSendingRequestEventBlob() throws StorageException, URISyntaxException, IOException {
        final int length = 128;

        final ArrayList<Boolean> callList = new ArrayList<Boolean>();
        OperationContext sendingRequestEventContext = new OperationContext();
        sendingRequestEventContext.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                assertEquals(eventArg.getRequestResult(), eventArg.getOpContext().getLastResult());
                callList.add(true);
            }
        });

        assertEquals(0, callList.size());

        //Put blob
        CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "bb", length,
                sendingRequestEventContext);

        assertEquals(1, callList.size());

        //Get blob
        blob.download(new ByteArrayOutputStream(), null, null, sendingRequestEventContext);
        assertEquals(2, callList.size());

        //uploadMetadata
        blob.uploadMetadata(null, null, sendingRequestEventContext);
        assertEquals(3, callList.size());

        //uploadMetadata
        blob.downloadAttributes(null, null, sendingRequestEventContext);
        assertEquals(4, callList.size());

    }

    /**
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobInputStream() throws URISyntaxException, StorageException, IOException {
        final int blobLength = 16 * 1024;
        final Random randGenerator = new Random();
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudBlockBlob blobRef = this.container.getBlockBlobReference(blobName);

        final byte[] buff = new byte[blobLength];
        randGenerator.nextBytes(buff);
        buff[0] = -1;
        buff[1] = -128;
        final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);

        final BlobRequestOptions options = new BlobRequestOptions();
        final OperationContext operationContext = new OperationContext();
        options.setStoreBlobContentMD5(true);
        options.setTimeoutIntervalInMs(90000);
        options.setRetryPolicyFactory(new RetryNoRetry());
        blobRef.uploadFullBlob(sourceStream, blobLength, null, options, operationContext);

        BlobInputStream blobStream = blobRef.openInputStream();

        for (int i = 0; i < blobLength; i++) {
            int data = blobStream.read();
            assertTrue(data >= 0);
            assertEquals(buff[i], (byte) data);
        }

        assertEquals(-1, blobStream.read());

        blobRef.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadFromByteArray() throws Exception {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);

        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 512, 0, 511);
    }

    private void doUploadFromByteArrayTest(CloudBlockBlob blob, int bufferSize, int bufferOffset, int count)
            throws Exception {
        byte[] buffer = BlobTestHelper.getRandomBuffer(bufferSize);
        byte[] downloadedBuffer = new byte[bufferSize];

        blob.uploadFromByteArray(buffer, bufferOffset, count);
        blob.downloadToByteArray(downloadedBuffer, 0);

        int i = 0;
        for (; i < count; i++) {
            assertEquals(buffer[i + bufferOffset], downloadedBuffer[i]);
        }

        for (; i < downloadedBuffer.length; i++) {
            assertEquals(0, downloadedBuffer[i]);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadDownloadFromFile() throws IOException, StorageException, URISyntaxException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);

        //this.doUploadDownloadFileTest(blob, 0);
        //this.doUploadDownloadFileTest(blob, 4096);
        //this.doUploadDownloadFileTest(blob, 4097);
        this.doUploadDownloadFileTest(blob, 5 * 1024 * 1024);
        //this.doUploadDownloadFileTest(blob, 11 * 1024 * 1024);
    }

    private void doUploadDownloadFileTest(CloudBlockBlob blob, int fileSize) throws IOException, StorageException {
        File sourceFile = File.createTempFile("sourceFile", ".tmp");
        File destinationFile = new File(sourceFile.getParentFile(), "destinationFile.tmp");

        try {

            byte[] buffer = BlobTestHelper.getRandomBuffer(fileSize);
            FileOutputStream fos = new FileOutputStream(sourceFile);
            fos.write(buffer);
            fos.close();
            blob.uploadFromFile(sourceFile.getAbsolutePath());

            blob.downloadToFile(destinationFile.getAbsolutePath());
            assertTrue("Destination file does not exist.", destinationFile.exists());
            assertEquals("Destination file does not match input file.", fileSize, destinationFile.length());
            FileInputStream fis = new FileInputStream(destinationFile);

            byte[] readBuffer = new byte[fileSize];
            fis.read(readBuffer);
            fis.close();

            for (int i = 0; i < fileSize; i++) {
                assertEquals("File contents do not match.", buffer[i], readBuffer[i]);
            }
        }
        finally {
            if (sourceFile.exists()) {
                sourceFile.delete();
            }

            if (destinationFile.exists()) {
                destinationFile.delete();
            }
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadDownloadFromText() throws URISyntaxException, StorageException, IOException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);

        this.doUploadDownloadStringTest(blob, 0);
        this.doUploadDownloadStringTest(blob, 8000);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobMultiConditionHeaders() throws URISyntaxException, StorageException, IOException {
        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudBlockBlob blockBlobRef = this.container.getBlockBlobReference(blockBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        OperationContext context = new OperationContext();
        blockBlobRef.upload(srcStream, -1, null, null, context);

        AccessCondition condition = AccessCondition.generateIfMatchCondition(context.getLastResult().getEtag());
        condition.setIfUnmodifiedSinceDate(context.getLastResult().getStartDate());

        StorageEvent<SendingRequestEvent> event = new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                assertNotNull(connection.getRequestProperty("If-Unmodified-Since"));
                assertNotNull(connection.getRequestProperty("If-Match"));
            }
        };

        context.getSendingRequestEventHandler().addListener(event);

        blockBlobRef.upload(srcStream, -1, condition, null, context);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testBlobConditionalAccess() throws StorageException, IOException, URISyntaxException {
        CloudBlockBlob blob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB,
                "test", 128, null);
        blob.downloadAttributes();

        String currentETag = blob.getProperties().getEtag();
        Date currentModifiedTime = blob.getProperties().getLastModified();

        // ETag conditional tests
        blob.getMetadata().put("ETagConditionalName", "ETagConditionalValue");
        blob.uploadMetadata(AccessCondition.generateIfMatchCondition(currentETag), null, null);

        blob.downloadAttributes();
        String newETag = blob.getProperties().getEtag();
        assertFalse(newETag.equals(currentETag));

        blob.getMetadata().put("ETagConditionalName", "ETagConditionalValue2");

        try {
            blob.uploadMetadata(AccessCondition.generateIfNoneMatchCondition(newETag), null, null);
            fail("If none match on conditional test should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        String invalidETag = "\"0x10101010\"";
        try {
            blob.uploadMetadata(AccessCondition.generateIfMatchCondition(invalidETag), null, null);
            fail("Invalid ETag on conditional test should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        currentETag = blob.getProperties().getEtag();
        blob.uploadMetadata(AccessCondition.generateIfNoneMatchCondition(invalidETag), null, null);

        blob.downloadAttributes();
        newETag = blob.getProperties().getEtag();

        // LastModifiedTime tests
        currentModifiedTime = blob.getProperties().getLastModified();

        blob.getMetadata().put("DateConditionalName", "DateConditionalValue");

        try {
            blob.uploadMetadata(AccessCondition.generateIfModifiedSinceCondition(currentModifiedTime), null, null);
            fail("IfModifiedSince conditional on current modified time should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        Calendar cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE) - 5);
        Date pastTime = cal.getTime();
        blob.uploadMetadata(AccessCondition.generateIfModifiedSinceCondition(pastTime), null, null);

        cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.HOUR, cal.get(Calendar.HOUR) - 5);
        pastTime = cal.getTime();
        blob.uploadMetadata(AccessCondition.generateIfModifiedSinceCondition(pastTime), null, null);

        cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) - 5);
        pastTime = cal.getTime();
        blob.uploadMetadata(AccessCondition.generateIfModifiedSinceCondition(pastTime), null, null);

        currentModifiedTime = blob.getProperties().getLastModified();

        cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE) - 5);
        pastTime = cal.getTime();
        try {
            blob.uploadMetadata(AccessCondition.generateIfNotModifiedSinceCondition(pastTime), null, null);
            fail("IfNotModifiedSince conditional on past time should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.HOUR, cal.get(Calendar.HOUR) - 5);
        pastTime = cal.getTime();
        try {
            blob.uploadMetadata(AccessCondition.generateIfNotModifiedSinceCondition(pastTime), null, null);
            fail("IfNotModifiedSince conditional on past time should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        cal = Calendar.getInstance();
        cal.setTime(currentModifiedTime);
        cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) - 5);
        pastTime = cal.getTime();
        try {
            blob.uploadMetadata(AccessCondition.generateIfNotModifiedSinceCondition(pastTime), null, null);
            fail("IfNotModifiedSince conditional on past time should throw");
        }
        catch (StorageException e) {
            assertEquals("ConditionNotMet", e.getErrorCode());
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, e.getHttpStatusCode());
            assertEquals("The condition specified using HTTP conditional header(s) is not met.", e.getMessage());
        }

        blob.getMetadata().put("DateConditionalName", "DateConditionalValue2");

        currentETag = blob.getProperties().getEtag();
        blob.uploadMetadata(AccessCondition.generateIfNotModifiedSinceCondition(currentModifiedTime), null, null);

        blob.downloadAttributes();
        newETag = blob.getProperties().getEtag();
        assertFalse("ETage should be modified on write metadata", newETag.equals(currentETag));
    }

    private void doUploadDownloadStringTest(CloudBlockBlob blob, int length) throws StorageException, IOException {
        String stringToUse = this.getRandomUNCString(length);
        blob.uploadText(stringToUse, Constants.UTF8_CHARSET, null, null, null);
        String newString = blob.downloadText(Constants.UTF8_CHARSET, null, null, null);
        assertEquals("Strings are not equal", stringToUse, newString);

        stringToUse = this.getRandomUNCString(length);
        blob.uploadText(stringToUse, "UTF-16", null, null, null);
        newString = blob.downloadText("UTF-16", null, null, null);
        assertEquals("Strings are not equal", stringToUse, newString);

        stringToUse = this.getRandomUNCString(length);
        blob.uploadText(stringToUse, "UTF-16BE", null, null, null);
        newString = blob.downloadText("UTF-16BE", null, null, null);
        assertEquals("Strings are not equal", stringToUse, newString);

        stringToUse = this.getRandomUNCString(length);
        blob.uploadText(stringToUse, "UTF-16LE", null, null, null);
        newString = blob.downloadText("UTF-16LE", null, null, null);
        assertEquals("Strings are not equal", stringToUse, newString);

        stringToUse = this.getRandomASCIIString(length);
        blob.uploadText(stringToUse, "US-ASCII", null, null, null);
        newString = blob.downloadText("US-ASCII", null, null, null);
        assertEquals("Strings are not equal", stringToUse, newString);
    }

    // Not a good test over all of Unicode, but good enough for our purposes
    private String getRandomUNCString(int length) {
        return this.getRandomString(length, 0xD7FF);
    }

    private String getRandomASCIIString(int length) {
        return this.getRandomString(length, 0x7F);
    }

    private String getRandomString(int length, int maxCodePoint) {
        int[] codePoints = new int[length];
        Random random = new Random(237);
        for (int i = 0; i < length; i++) {
            codePoints[i] = random.nextInt(maxCodePoint);
        }

        return new String(codePoints, 0, length);
    }

    private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) throws URISyntaxException,
    StorageException, IOException, InvalidKeyException, InterruptedException {

        // Create source on server.
        CloudBlockBlob source = this.container.getBlockBlobReference("source");

        String data = "String data";
        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        // Get destination reference
        CloudBlockBlob destination = this.container.getBlockBlobReference("destination");
        destination.commitBlockList(new ArrayList<BlockEntry>());

        CloudBlockBlob copySource = source;
        CloudBlockBlob copyDestination = destination;

        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        cal.setTime(new Date());
        cal.add(Calendar.HOUR, 1);

        if (sourceIsSas) {
            // Source SAS must have read permissions
            SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
            policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE));
            policy.setSharedAccessExpiryTime(cal.getTime());

            BlobContainerPermissions perms = new BlobContainerPermissions();
            perms.getSharedAccessPolicies().put("read", policy);
            this.container.uploadPermissions(perms);
            Thread.sleep(30000);

            String sasToken = source.generateSharedAccessSignature(policy, null);

            // Get source BlockBlob reference
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            copySource = new CloudBlockBlob(credentials.transformUri(source.getUri()));
        }

        if (destinationIsSas) {
            // Destination SAS must have write permissions
            SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
            policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE));
            policy.setSharedAccessExpiryTime(cal.getTime());

            BlobContainerPermissions perms = new BlobContainerPermissions();
            // Source container must be public if source is not SAS
            if (!sourceIsSas) {
                perms.setPublicAccess(BlobContainerPublicAccessType.BLOB);
            }
            perms.getSharedAccessPolicies().put("write", policy);
            this.container.uploadPermissions(perms);
            Thread.sleep(30000);

            String sasToken = destination.generateSharedAccessSignature(policy, null);

            // Get destination block blob reference
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            copyDestination = new CloudBlockBlob(credentials.transformUri(destination.getUri()));
        }

        Thread.sleep(30000);

        // Start copy and wait for completion
        String copyId = copyDestination.startCopy(copySource);
        BlobTestHelper.waitForCopy(copyDestination);
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        destination.downloadAttributes();

        // Check original blob references for equality
        assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), destination.getCopyState().getSource().getPath());
        assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue());
        assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, destination.getProperties().getCopyState().getCopyId());
        assertTrue(destination.getCopyState().getCompletionTime()
                .compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        if (!destinationIsSas) {
            try {
                copyDestination.abortCopy(destination.getCopyState().getCopyId());
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
            }
        }

        source.downloadAttributes();
        assertNotNull(destination.getProperties().getEtag());
        assertFalse(source.getProperties().getEtag().equals(destination.getProperties().getEtag()));
        assertTrue(destination.getProperties().getLastModified().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null);
        assertEquals(data, copyData);

        BlobProperties prop1 = destination.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", destination.getMetadata().get("Test"));

        destination.delete();
        source.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobUploadStandardTier() throws StorageException, IOException, URISyntaxException {
        for (StandardBlobTier standardBlobTier : StandardBlobTier.values()) {
            if (standardBlobTier == StandardBlobTier.UNKNOWN) {
                continue;
            }

            final String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob");
            final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName);
            blob.uploadText("text");

            blob.uploadStandardBlobTier(standardBlobTier);
            assertEquals(standardBlobTier, blob.getProperties().getStandardBlobTier());
            assertNull(blob.getProperties().getPremiumPageBlobTier());
            assertNull(blob.getProperties().getRehydrationStatus());

            CloudBlockBlob blob2 = this.container.getBlockBlobReference(blobName);
            blob2.downloadAttributes();
            assertEquals(standardBlobTier, blob2.getProperties().getStandardBlobTier());
            assertNull(blob2.getProperties().getPremiumPageBlobTier());
            assertNull(blob2.getProperties().getRehydrationStatus());

            CloudBlockBlob blob3 = (CloudBlockBlob)this.container.listBlobs().iterator().next();
            assertEquals(standardBlobTier, blob3.getProperties().getStandardBlobTier());
            assertNull(blob3.getProperties().getPremiumPageBlobTier());
            assertNull(blob3.getProperties().getRehydrationStatus());

            blob.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlockBlobRehydrateBlob() throws StorageException, IOException, URISyntaxException {
        final String blobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob1");
        final String blobName2 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob2");
        final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName1);
        blob.uploadText("text");
        blob.uploadStandardBlobTier(StandardBlobTier.ARCHIVE);
        final CloudBlockBlob blob2 = this.container.getBlockBlobReference(blobName2);
        blob2.uploadText("text");
        blob2.uploadStandardBlobTier(StandardBlobTier.ARCHIVE);

        CloudBlockBlob blobRef1 = this.container.getBlockBlobReference(blobName1);
        blobRef1.uploadStandardBlobTier(StandardBlobTier.COOL);
        assertNull(blobRef1.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, blobRef1.getProperties().getStandardBlobTier());
        assertNull(blobRef1.getProperties().getPremiumPageBlobTier());

        blob.downloadAttributes();
        assertEquals(RehydrationStatus.PENDING_TO_COOL, blob.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, blob.getProperties().getStandardBlobTier());
        assertNull(blob.getProperties().getPremiumPageBlobTier());

        CloudBlockBlob blobRef2 = this.container.getBlockBlobReference(blobName2);
        blobRef2.uploadStandardBlobTier(StandardBlobTier.HOT);
        assertNull(blobRef2.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, blobRef2.getProperties().getStandardBlobTier());
        assertNull(blobRef2.getProperties().getPremiumPageBlobTier());

        blob2.downloadAttributes();
        assertEquals(RehydrationStatus.PENDING_TO_HOT, blob2.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, blob2.getProperties().getStandardBlobTier());
        assertNull(blob2.getProperties().getPremiumPageBlobTier());

        Iterator it = this.container.listBlobs().iterator();
        CloudBlockBlob listBlob = (CloudBlockBlob)it.next();
        assertEquals(RehydrationStatus.PENDING_TO_COOL, listBlob.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, listBlob.getProperties().getStandardBlobTier());
        assertNull(listBlob.getProperties().getPremiumPageBlobTier());

        CloudBlockBlob listBlob2 = (CloudBlockBlob)it.next();
        assertEquals(RehydrationStatus.PENDING_TO_HOT, listBlob2.getProperties().getRehydrationStatus());
        assertEquals(StandardBlobTier.ARCHIVE, listBlob2.getProperties().getStandardBlobTier());
        assertNull(listBlob2.getProperties().getPremiumPageBlobTier());
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class CloudPageBlobTests {
    protected CloudBlobContainer container;

    @Before
    public void pageBlobTestMethodSetUp() throws URISyntaxException, StorageException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();
    }

    @After
    public void pageBlobTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    /**
     * Start copying a blob and then abort
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCopyFromPageBlobAbortTest() throws StorageException, URISyntaxException, IOException {
        final int length = 512;
        CloudPageBlob originalBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.PAGE_BLOB, "originalBlob", length, null);
        CloudPageBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed");
        copyBlob.startCopy(originalBlob);

        try {
            copyBlob.abortCopy(copyBlob.getProperties().getCopyState().getCopyId());
        }
        catch (StorageException e) {
            if (!e.getErrorCode().contains("NoPendingCopyOperation")) {
                throw e;
            }
        }
    }

    /**
     * Create a snapshot
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testPageBlobSnapshotValidationTest() throws StorageException, URISyntaxException, IOException{
        final int length = 1024;
        CloudPageBlob blockBlobRef = (CloudPageBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB,
                "originalBlob", length, null);
        final CloudBlob blobSnapshot = blockBlobRef.createSnapshot();

        for (ListBlobItem blob : this.container.listBlobs(null, true, EnumSet.allOf(BlobListingDetails.class), null,
                null)) {
            final ByteArrayOutputStream outStream = new ByteArrayOutputStream(length);
            ((CloudBlob) blob).download(outStream);
        }

        ByteArrayOutputStream outStream = new ByteArrayOutputStream(length);

        blobSnapshot.download(outStream);
        byte[] retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);

        // Read operation should work fine.
        blobSnapshot.downloadAttributes();

        final CloudPageBlob blobSnapshotUsingRootUri = this.container.getPageBlobReference(blockBlobRef.getName(),
                blobSnapshot.getSnapshotID());
        outStream = new ByteArrayOutputStream(length);

        blobSnapshotUsingRootUri.download(outStream);
        retrievedBuff = outStream.toByteArray();
        assertEquals(length, retrievedBuff.length);
        assertEquals(blobSnapshot.getSnapshotID(), blobSnapshotUsingRootUri.getSnapshotID());

        // Expect an IllegalArgumentException from upload.
        try {
            final Random randGenerator = new Random();
            final byte[] buff = new byte[length];
            randGenerator.nextBytes(buff);
            blobSnapshot.upload(new ByteArrayInputStream(buff), -1);
            fail("Expect an IllegalArgumentException from upload");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadMetadata.
        try {
            blobSnapshot.uploadMetadata();
            fail("Expect an IllegalArgumentException from uploadMetadata");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from uploadProperties.
        try {
            blobSnapshot.uploadProperties();
            fail("Expect an IllegalArgumentException from uploadProperties");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }

        // Expect an IllegalArgumentException from createSnapshot.
        try {
            blobSnapshot.createSnapshot();
            fail("Expect an IllegalArgumentException from createSnapshot");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Cannot perform this operation on a blob representing a snapshot.", e.getMessage());
        }
    }

    /**
     * Create a blob and try to download a range of its contents
     *
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testPageBlobDownloadRangeValidationTest() throws StorageException, URISyntaxException, IOException{
        final int length = 5 * 1024 * 1024;

        final String blockBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(blockBlobName);

        pageBlobRef.upload(BlobTestHelper.getRandomDataStream(length), length);

        //Download full blob
        pageBlobRef.download(new ByteArrayOutputStream());
        assertEquals(length, pageBlobRef.getProperties().getLength());

        //Download blob range.
        byte[] downloadBuffer = new byte[100];
        int downloadLength = pageBlobRef.downloadRangeToByteArray(0, (long) 100, downloadBuffer, 0);
        assertEquals(length, pageBlobRef.getProperties().getLength());
        assertEquals(100, downloadLength);
    }

    /**
     * Test requesting stored content MD5 with OpenWriteExisting().
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testPageOpenWriteExistingWithMD5() throws URISyntaxException, StorageException, IOException {
        final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob");
        final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(pageBlobName);
        pageBlobRef.create(512);

        BlobRequestOptions options = new BlobRequestOptions();
        options.setStoreBlobContentMD5(true);
        options.setDisableContentMD5Validation(false);

        try
        {
            pageBlobRef.openWriteExisting(null, options, null);
            fail("Expect failure due to requesting MD5 calculation");
        }
        catch (IllegalArgumentException e)
        {
        }
    }

    @Test
    public void testPageBlobUploadFromStreamTest() throws URISyntaxException, StorageException, IOException {
        final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob");
        final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(pageBlobName);

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        pageBlobRef.upload(srcStream, length);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        pageBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = BlobTestHelper.getRandomDataStream(length);
        pageBlobRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        pageBlobRef.download(dstStream);
        BlobTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));
    }

    @Test
    public void testBlobUploadWithoutMD5Validation() throws URISyntaxException, StorageException, IOException {
        final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob");
        final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(pageBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(false);
        options.setStoreBlobContentMD5(false);

        pageBlobRef.upload(srcStream, length, null, options, null);
        pageBlobRef.downloadAttributes();
        pageBlobRef.getProperties().setContentMD5("MDAwMDAwMDA=");
        pageBlobRef.uploadProperties(null, options, null);

        try {
            pageBlobRef.download(new ByteArrayOutputStream(), null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(306, ex.getHttpStatusCode());
            assertEquals("InvalidMd5", ex.getErrorCode());
        }

        options.setDisableContentMD5Validation(true);
        pageBlobRef.download(new ByteArrayOutputStream(), null, options, null);
        
        final CloudPageBlob pageBlobRef2 = this.container.getPageBlobReference(pageBlobName);
        assertNull(pageBlobRef2.getProperties().getContentMD5());

        byte[] target = new byte[4];
        pageBlobRef2.downloadRangeToByteArray(0L, 4L, target, 0);
        assertEquals("MDAwMDAwMDA=", pageBlobRef2.properties.getContentMD5());
    }

    @Test
    public void testBlobEmptyHeaderSigningTest() throws URISyntaxException, StorageException, IOException {
        final String pageBlobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testPageBlob");
        final CloudPageBlob pageBlobRef = this.container.getPageBlobReference(pageBlobName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);

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

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                connection.setRequestProperty("x-ms-foo", "");
            }
        });

        pageBlobRef.upload(srcStream, length, null, null, context);
        pageBlobRef.download(new ByteArrayOutputStream(), null, null, context);
    }

    @Test
    public void testPageBlobDownloadRangeTest() throws URISyntaxException, StorageException, IOException {
        byte[] buffer = BlobTestHelper.getRandomBuffer(2 * 1024);

        CloudPageBlob blob = this.container.getPageBlobReference("blob1");
        ByteArrayInputStream wholeBlob = new ByteArrayInputStream(buffer);
        blob.upload(wholeBlob, 2 * 1024);

        ByteArrayOutputStream blobStream = new ByteArrayOutputStream();
        try {
            blob.downloadRange(0, new Long(0), blobStream);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        blob.downloadRange(0, new Long(1024), blobStream);
        assertEquals(blobStream.size(), 1024);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(blobStream.toByteArray()), wholeBlob, 0,
                0, 1024, 2 * 1024);

        CloudPageBlob blob2 = this.container.getPageBlobReference("blob1");
        try {
            blob.downloadRange(1024, new Long(0), blobStream);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        ByteArrayOutputStream blobStream2 = new ByteArrayOutputStream();
        blob2.downloadRange(1024, new Long(1024), blobStream2);
        BlobTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(blobStream2.toByteArray()), wholeBlob,
                1024, 1024, 1024, 2 * 1024);

        BlobTestHelper.assertAreEqual(blob, blob2);
    }

    @Test
    public void testCloudPageBlobDownloadToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudPageBlob blob = this.container.getPageBlobReference("blob1");
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 0);
        BlobTestHelper.doDownloadTest(blob, 1 * 512, 2 * 512, 1 * 512);
        BlobTestHelper.doDownloadTest(blob, 2 * 512, 4 * 512, 1 * 512);
        BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 5 * 1024 * 1024, 0);
        BlobTestHelper.doDownloadTest(blob, 5 * 1024 * 1024, 6 * 1024 * 1024, 512);
    }

    @Test
    public void testCloudPageBlobDownloadRangeToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudPageBlob blob = this.container.getPageBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("downloadrange"));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(1 * 1024 * 1024), new Long(5 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 2 * 1024 * 1024,
                new Long(2 * 1024 * 1024), new Long(6 * 1024 * 1024));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(4 * 1024 * 1024), new Long(4 * 1024 * 1024));

        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 0, new Long(1 * 512), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(0), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(1 * 512), null);
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, new Long(0), new Long(1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                1 * 512));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                2 * 512));

        // Edge cases
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 1023, new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(1023), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(0), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 0, new Long(512), new Long(1));
        BlobTestHelper.doDownloadRangeToByteArrayTest(blob, 1024, 1024, 512, new Long(1023), new Long(1));
    }

    @Test
    public void testCloudPageBlobDownloadRangeToByteArrayNegativeTest() throws URISyntaxException, StorageException,
            IOException {
        CloudPageBlob blob = this.container.getPageBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("downloadrangenegative"));
        BlobTestHelper.doDownloadRangeToByteArrayNegativeTests(blob);
    }

    @Test
    public void testCloudPageBlobUploadFromStreamWithAccessCondition() throws URISyntaxException, StorageException,
            IOException {
        CloudPageBlob blob1 = this.container.getPageBlobReference("blob1");
        AccessCondition accessCondition = AccessCondition.generateIfNoneMatchCondition("\"*\"");
        final int length = 6 * 512;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        blob1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        blob1.create(1024);
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob1.getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfMatchCondition(blob1.getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        CloudPageBlob blob2 = this.container.getPageBlobReference("blob2");
        blob2.create(1024);
        accessCondition = AccessCondition.generateIfMatchCondition(blob1.getProperties().getEtag());
        try {
            blob1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(blob2.getProperties().getEtag());
        blob1.upload(srcStream, length, accessCondition, null, null);
    }

    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testPageBlobNamePlusEncodingTest() throws StorageException, URISyntaxException, IOException,
            InterruptedException {
        final int length = 1 * 1024;

        final CloudPageBlob originalBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob(this.container,
                BlobType.PAGE_BLOB, "a+b.txt", length, null);
        final CloudPageBlob copyBlob = this.container.getPageBlobReference(originalBlob.getName() + "copyed");

        copyBlob.startCopy(originalBlob);
        BlobTestHelper.waitForCopy(copyBlob);
        copyBlob.downloadAttributes();
    }

    /**
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    public void testPageBlobInputStream() throws URISyntaxException, StorageException, IOException {
        final int blobLength = 16 * 1024;
        final Random randGenerator = new Random();
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);

        final byte[] buff = new byte[blobLength];
        randGenerator.nextBytes(buff);
        buff[0] = -1;
        buff[1] = -128;
        final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);

        final BlobRequestOptions options = new BlobRequestOptions();
        final OperationContext operationContext = new OperationContext();
        options.setTimeoutIntervalInMs(90000);
        options.setRetryPolicyFactory(new RetryNoRetry());
        blobRef.upload(sourceStream, blobLength, null, options, operationContext);

        BlobInputStream blobStream = blobRef.openInputStream();

        for (int i = 0; i < blobLength; i++) {
            int data = blobStream.read();
            assertTrue(data >= 0);
            assertEquals(buff[i], (byte) data);
        }

        assertEquals(-1, blobStream.read());

        blobRef.delete();
    }

    @Test
    public void testUploadFromByteArray() throws Exception {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blob = this.container.getPageBlobReference(blobName);

        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512);
        this.doUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512);
    }

    @Test
    public void testUploadDownloadFromFile() throws IOException, StorageException, URISyntaxException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blob = this.container.getPageBlobReference(blobName);

        this.doUploadDownloadFileTest(blob, 512);
        this.doUploadDownloadFileTest(blob, 4096);
        this.doUploadDownloadFileTest(blob, 5 * 1024 * 1024);
        this.doUploadDownloadFileTest(blob, 11 * 1024 * 1024);
    }

    @Test
    public void testPageBlobCopyTest() throws URISyntaxException, StorageException, InterruptedException, IOException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);

        CloudPageBlob source = this.container.getPageBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudPageBlob copy = this.container.getPageBlobReference("copy");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(source));
        BlobTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), copy.getCopyState().getSource().getPath());
        assertEquals(buffer.length, copy.getCopyState().getTotalBytes().intValue());
        assertEquals(buffer.length, copy.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        try {
            copy.abortCopy(copy.getCopyState().getCopyId());
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        source.downloadAttributes();
        assertNotNull(copy.getProperties().getEtag());
        assertFalse(source.getProperties().getEtag().equals(copy.getProperties().getEtag()));
        assertTrue(copy.getProperties().getLastModified().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(copyStream.toByteArray()));

        copy.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copy.getMetadata().get("Test"));

        copy.delete();
    }

    @Test
    public void testPageBlobCopyWithMetadataOverride() throws URISyntaxException, StorageException, IOException,
            InterruptedException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        CloudPageBlob source = this.container.getPageBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);

        source.upload(stream, buffer.length);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudPageBlob copy = this.container.getPageBlobReference("copy");
        copy.getMetadata().put("Test2", "value2");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(source));
        BlobTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getSnapshotQualifiedUri().getPath(), copy.getCopyState().getSource().getPath());
        assertEquals(buffer.length, copy.getCopyState().getTotalBytes().intValue());
        assertEquals(buffer.length, copy.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(copyStream.toByteArray()));

        copy.downloadAttributes();
        source.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value2", copy.getMetadata().get("Test2"));
        assertFalse(copy.getMetadata().containsKey("Test"));

        copy.delete();
    }

    @Test
    public void testPageBlobCopyFromSnapshot() throws StorageException, IOException, URISyntaxException,
            InterruptedException {
        CloudPageBlob source = this.container.getPageBlobReference("source");

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);

        source.upload(stream, buffer.length);

        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        CloudPageBlob snapshot = (CloudPageBlob) source.createSnapshot();

        //Modify source
        byte[] buffer2 = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream2 = new ByteArrayInputStream(buffer2);
        source.getMetadata().put("Test", "newvalue");
        source.uploadMetadata();
        source.getProperties().setContentMD5(null);
        source.upload(stream2, buffer.length);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        source.download(outputStream);

        ByteArrayOutputStream snapshotStream = new ByteArrayOutputStream();
        snapshot.download(snapshotStream);
        BlobTestHelper.assertStreamsAreEqual(stream2, new ByteArrayInputStream(outputStream.toByteArray()));
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(snapshotStream.toByteArray()));

        source.downloadAttributes();
        snapshot.downloadAttributes();
        assertFalse(source.getMetadata().get("Test").equals(snapshot.getMetadata().get("Test")));

        CloudPageBlob copy = this.container.getPageBlobReference("copy");
        String copyId = copy.startCopy(BlobTestHelper.defiddler(snapshot));
        BlobTestHelper.waitForCopy(copy);

        ByteArrayOutputStream copyStream = new ByteArrayOutputStream();
        copy.download(copyStream);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        BlobTestHelper.assertStreamsAreEqual(stream, new ByteArrayInputStream(copyStream.toByteArray()));
        assertEquals(copyId, copy.getProperties().getCopyState().getCopyId());

        copy.downloadAttributes();
        BlobProperties prop1 = copy.getProperties();
        BlobProperties prop2 = snapshot.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copy.getMetadata().get("Test"));

        copy.delete();
    }

    private void doUploadFromByteArrayTest(CloudPageBlob blob, int bufferSize, int bufferOffset, int count)
            throws Exception {
        byte[] buffer = BlobTestHelper.getRandomBuffer(bufferSize);
        byte[] downloadedBuffer = new byte[bufferSize];

        blob.uploadFromByteArray(buffer, bufferOffset, count);
        blob.downloadToByteArray(downloadedBuffer, 0);

        int i = 0;
        for (; i < count; i++) {
            assertEquals(buffer[i + bufferOffset], downloadedBuffer[i]);
        }

        for (; i < downloadedBuffer.length; i++) {
            assertEquals(0, downloadedBuffer[i]);
        }
    }

    private void doUploadDownloadFileTest(CloudPageBlob blob, int fileSize) throws IOException, StorageException {
        File sourceFile = File.createTempFile("sourceFile", ".tmp");
        File destinationFile = new File(sourceFile.getParentFile(), "destinationFile.tmp");

        try {

            byte[] buffer = BlobTestHelper.getRandomBuffer(fileSize);
            FileOutputStream fos = new FileOutputStream(sourceFile);
            fos.write(buffer);
            fos.close();
            blob.uploadFromFile(sourceFile.getAbsolutePath());

            blob.downloadToFile(destinationFile.getAbsolutePath());
            assertTrue("Destination file does not exist.", destinationFile.exists());
            assertEquals("Destination file does not match input file.", fileSize, destinationFile.length());
            FileInputStream fis = new FileInputStream(destinationFile);

            byte[] readBuffer = new byte[fileSize];
            fis.read(readBuffer);
            fis.close();

            for (int i = 0; i < fileSize; i++) {
                assertEquals("File contents do not match.", buffer[i], readBuffer[i]);
            }
        }
        finally {
            if (sourceFile.exists()) {
                sourceFile.delete();
            }

            if (destinationFile.exists()) {
                destinationFile.delete();
            }
        }
    }

    @Test
    public void testUploadPages() throws URISyntaxException, StorageException, IOException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512);

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);
        blobRef.create(blobLengthToUse);
        assertNull(blobRef.getProperties().getPageBlobSequenceNumber());

        // Upload one page (page 0)
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        blobRef.uploadPages(inputStream, 0, 512);
        assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber());

        // Upload pages 2-4
        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        blobRef.uploadPages(inputStream, 2 * 512, 3 * 512);
        assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber());

        // Now, we expect the first 512 bytes of the blob to be the first 512 bytes of the random buffer (page 0)
        // the next 512 bytes should be 0 (page 1)
        // The next 3 * 512 bytes should be equal to bytes (512 -> 4 * 512) of the random buffer (pages 2-4)
        // The next 3 * 512 bytes should be 0 (pages 5-7)

        byte[] result = new byte[blobLengthToUse];
        blobRef.downloadToByteArray(result, 0);

        for (int i = 0; i < 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (int i = 0; i < 512; i++) {
            assertEquals(0, result[i + 512]);
        }

        for (int i = 0; i < 3 * 512; i++) {
            assertEquals(buffer[i + 512], result[i + 2 * 512]);
        }

        for (int i = 0; i < 3 * 512; i++) {
            assertEquals(0, result[i + 5 * 512]);
        }

        inputStream = new ByteArrayInputStream(buffer);

        try {
            blobRef.uploadPages(inputStream, 0, 256);
            fail("Did not throw expected exception on non-512-byte-aligned length");
        }
        catch (IllegalArgumentException ex) {
            assertEquals(SR.INVALID_PAGE_BLOB_LENGTH, ex.getMessage());
        }

        try {
            blobRef.uploadPages(inputStream, 3 * 256, 3 * 512);
            fail("Did not throw expected exception on non-512-byte-aligned offset");
        }
        catch (IllegalArgumentException ex) {
            assertEquals(SR.INVALID_PAGE_START_OFFSET, ex.getMessage());
        }
    }

    @Test
    public void testClearPages() throws URISyntaxException, StorageException, IOException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512);

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);
        blobRef.create(blobLengthToUse);
        assertNull(blobRef.getProperties().getPageBlobSequenceNumber());

        // Upload one page (page 0)
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        blobRef.uploadPages(inputStream, 0, blobLengthToUse);
        assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber());

        try {
            blobRef.clearPages(0, 256);
            fail("Did not throw expected exception on non-512-byte-aligned length");
        }
        catch (IllegalArgumentException ex) {
            assertEquals(SR.INVALID_PAGE_BLOB_LENGTH, ex.getMessage());
        }

        try {
            blobRef.clearPages(3 * 256, 3 * 512);
            fail("Did not throw expected exception on non-512-byte-aligned offset");
        }
        catch (IllegalArgumentException ex) {
            assertEquals(SR.INVALID_PAGE_START_OFFSET, ex.getMessage());
        }

        blobRef.clearPages(3 * 512, 2 * 512);
        assertNotNull(blobRef.getProperties().getPageBlobSequenceNumber());

        byte[] result = new byte[blobLengthToUse];
        blobRef.downloadToByteArray(result, 0);

        int i = 0;

        for (; i < 3 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (; i < 5 * 512; i++) {
            assertEquals(0, result[i]);
        }

        for (; i < 8 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }
    }

    @Test
    public void testResize() throws StorageException, URISyntaxException {
        CloudPageBlob blob = this.container.getPageBlobReference("blob1");
        CloudPageBlob blob2 = this.container.getPageBlobReference("blob1");

        blob.create(1024);
        assertEquals(1024, blob.getProperties().getLength());
        assertNull(blob.getProperties().getPageBlobSequenceNumber());

        blob2.downloadAttributes();
        assertEquals(1024, blob2.getProperties().getLength());
        assertNull(blob.getProperties().getPageBlobSequenceNumber());

        blob2.getProperties().setContentType("text/plain");
        blob2.uploadProperties();

        blob.resize(2048);
        assertEquals(2048, blob.getProperties().getLength());
        assertNotNull(blob.getProperties().getPageBlobSequenceNumber());

        blob.downloadAttributes();
        assertEquals("text/plain", blob.getProperties().getContentType());
        assertNotNull(blob.getProperties().getPageBlobSequenceNumber());

        blob2.downloadAttributes();
        assertEquals(2048, blob2.getProperties().getLength());
        assertNotNull(blob.getProperties().getPageBlobSequenceNumber());
    }

    private CloudPageBlob setUpPageRanges() throws StorageException, URISyntaxException, IOException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(blobLengthToUse);

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);
        blobRef.create(blobLengthToUse);

        // Upload page 0
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        blobRef.uploadPages(inputStream, 0, 512);

        // Upload pages 2-4
        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        blobRef.uploadPages(inputStream, 2 * 512, 3 * 512);

        // Upload page 6
        inputStream = new ByteArrayInputStream(buffer, 3 * 512, 512);
        blobRef.uploadPages(inputStream, 6 * 512, 512);

        // Page0: 512 bytes should be the first 512 bytes of the random buffer (page 0)
        // Page1: 512 bytes should be 0
        // Page2-4: 3 * 512 bytes should be equal to bytes (512 -> 4 * 512) of the random buffer (pages 2-4)
        // Page5: 512 bytes should be 0
        // Page6: 512 bytes should be the 4th 512 byte segmented of the random buffer
        // Page7-8: 2 * 512 bytes should be 0
        return blobRef;
    }

    @Test
    public void testDownloadPages() throws StorageException, URISyntaxException, IOException {
        final CloudPageBlob blobRef = setUpPageRanges();

        ArrayList<PageRange> actualPageRanges = blobRef.downloadPageRanges();
        ArrayList<PageRange> expectedPageRanges = new ArrayList<PageRange>();
        expectedPageRanges.add(new PageRange(0, 512 - 1));
        expectedPageRanges.add(new PageRange(2 * 512, 5 * 512 - 1));
        expectedPageRanges.add(new PageRange(6 * 512, 7 * 512 - 1));

        assertEquals(expectedPageRanges.size(), actualPageRanges.size());
        for (int i = 0; i < expectedPageRanges.size(); i++) {
            assertEquals(expectedPageRanges.get(i).getStartOffset(), actualPageRanges.get(i).getStartOffset());
            assertEquals(expectedPageRanges.get(i).getEndOffset(), actualPageRanges.get(i).getEndOffset());
        }
    }

    @Test
    public void testDownloadPageRangeDiff() throws StorageException, URISyntaxException, IOException {
        final CloudPageBlob blobRef = setUpPageRanges();
        final CloudPageBlob snapshot = (CloudPageBlob) blobRef.createSnapshot();

        // Add page 1
        InputStream inputStream = new ByteArrayInputStream(BlobTestHelper.getRandomBuffer(512));
        inputStream = new ByteArrayInputStream(BlobTestHelper.getRandomBuffer(512));
        blobRef.uploadPages(inputStream, 0, 512);

        // Clear page 6
        blobRef.clearPages(6 * 512, 512);

        List<PageRangeDiff> actualPageRanges = blobRef.downloadPageRangesDiff(snapshot.getSnapshotID());
        List<PageRangeDiff> expectedPageRanges = new ArrayList<PageRangeDiff>();
        expectedPageRanges.add(new PageRangeDiff(0, 512 - 1, false));
        expectedPageRanges.add(new PageRangeDiff(6 * 512, 7 * 512 - 1, true));

        assertEquals(expectedPageRanges.size(), actualPageRanges.size());
        for (int i = 0; i < expectedPageRanges.size(); i++) {
            assertEquals(expectedPageRanges.get(i).getStartOffset(), actualPageRanges.get(i).getStartOffset());
            assertEquals(expectedPageRanges.get(i).getEndOffset(), actualPageRanges.get(i).getEndOffset());
            assertEquals(expectedPageRanges.get(i).isCleared(), actualPageRanges.get(i).isCleared());
        }
    }

    @Test
    public void testDownloadPageRangesWithOffsetAndLength() throws StorageException, URISyntaxException, IOException {
        final CloudPageBlob blobRef = setUpPageRanges();

        List<PageRange> actualPageRanges = blobRef.downloadPageRanges((long)1*512, (long)5*512);
        List<PageRange> expectedPageRanges = new ArrayList<PageRange>();
        expectedPageRanges.add(new PageRange(2 * 512, 5 * 512 - 1));

        assertEquals(expectedPageRanges.size(), actualPageRanges.size());
        for (int i = 0; i < expectedPageRanges.size(); i++) {
            assertEquals(expectedPageRanges.get(i).getStartOffset(), actualPageRanges.get(i).getStartOffset());
            assertEquals(expectedPageRanges.get(i).getEndOffset(), actualPageRanges.get(i).getEndOffset());
        }
    }

    @Test
    public void testDownloadPageRangesWithOffset() throws StorageException, URISyntaxException, IOException {
        final CloudPageBlob blobRef = setUpPageRanges();

        List<PageRange> actualPageRanges = blobRef.downloadPageRanges((long)1*512, null);
        List<PageRange> expectedPageRanges = new ArrayList<PageRange>();
        expectedPageRanges.add(new PageRange(2 * 512, 5 * 512 - 1));
        expectedPageRanges.add(new PageRange(6 * 512, 7 * 512 - 1));

        assertEquals(expectedPageRanges.size(), actualPageRanges.size());
        for (int i = 0; i < expectedPageRanges.size(); i++) {
            assertEquals(expectedPageRanges.get(i).getStartOffset(), actualPageRanges.get(i).getStartOffset());
            assertEquals(expectedPageRanges.get(i).getEndOffset(), actualPageRanges.get(i).getEndOffset());
        }
    }

    @Test
    public void testDownloadPageRangeDiffWithOffsetAndLength() throws StorageException, URISyntaxException, IOException {
        final CloudPageBlob blobRef = setUpPageRanges();
        final CloudPageBlob snapshot = (CloudPageBlob) blobRef.createSnapshot();

        // Add page 1
        InputStream inputStream = new ByteArrayInputStream(BlobTestHelper.getRandomBuffer(512));
        blobRef.uploadPages(inputStream, 0, 512);

        List<PageRangeDiff> actualPageRanges = blobRef.downloadPageRangesDiff(snapshot.getSnapshotID(), (long) 0,
                (long) 5 * 512, null, null, null);

        List<PageRangeDiff> expectedPageRanges = new ArrayList<PageRangeDiff>();
        expectedPageRanges.add(new PageRangeDiff(0, 512 - 1, false));

        assertEquals(expectedPageRanges.size(), actualPageRanges.size());
        for (int i = 0; i < expectedPageRanges.size(); i++) {
            assertEquals(expectedPageRanges.get(i).getStartOffset(), actualPageRanges.get(i).getStartOffset());
            assertEquals(expectedPageRanges.get(i).getEndOffset(), actualPageRanges.get(i).getEndOffset());
            assertEquals(expectedPageRanges.get(i).isCleared(), actualPageRanges.get(i).isCleared());
        }
    }

    @Test
    public void testUploadDownloadBlobProperties() throws URISyntaxException, StorageException, IOException {
        final int length = 512;

        // do this to make sure the set MD5 can be compared without an exception being thrown
        BlobRequestOptions options = new BlobRequestOptions();
        options.setDisableContentMD5Validation(true);

        // with explicit upload/download of properties
        String pageBlobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        CloudPageBlob pageBlobRef1 = this.container.getPageBlobReference(pageBlobName1);

        pageBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), length);

        BlobTestHelper.setBlobProperties(pageBlobRef1);
        BlobProperties props1 = pageBlobRef1.getProperties();
        pageBlobRef1.uploadProperties();

        pageBlobRef1.downloadAttributes(null, options, null);
        BlobProperties props2 = pageBlobRef1.getProperties();

        BlobTestHelper.assertAreEqual(props1, props2);

        // by uploading/downloading the blob
        pageBlobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlockBlob");
        pageBlobRef1 = this.container.getPageBlobReference(pageBlobName1);

        BlobTestHelper.setBlobProperties(pageBlobRef1);
        props1 = pageBlobRef1.getProperties();

        pageBlobRef1.upload(BlobTestHelper.getRandomDataStream(length), length);

        pageBlobRef1.download(new ByteArrayOutputStream(), null, options, null);
        props2 = pageBlobRef1.getProperties();

        BlobTestHelper.assertAreEqual(props1, props2);
    }

    @Test
    public void testOpenOutputStreamNotAligned() throws StorageException, URISyntaxException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512);

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);
        blobRef.create(blobLengthToUse);

        try {
            blobRef.openWriteNew(blobLengthToUse + 1);
            fail("Did not throw expected exception on non-512-byte-aligned offset");
        }
        catch (IllegalArgumentException ex) {
            assertEquals(SR.INVALID_PAGE_BLOB_LENGTH, ex.getMessage());
        }

        BlobOutputStream blobOutputStream = blobRef.openWriteNew(blobLengthToUse);

        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);

        try {
            blobOutputStream.write(inputStream, 511);
            blobOutputStream.close();
            fail("Did not throw expected exception on non-512-byte-aligned length");
        }
        catch (IOException ex) {
            assertEquals(String.format(SR.INVALID_NUMBER_OF_BYTES_IN_THE_BUFFER, 511), ex.getMessage());
        }
    }

    @Test
    public void testOpenOutputStream() throws URISyntaxException, StorageException, IOException {
        int blobLengthToUse = 8 * 512;
        byte[] buffer = BlobTestHelper.getRandomBuffer(8 * 512);

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        final CloudPageBlob blobRef = this.container.getPageBlobReference(blobName);
        blobRef.create(blobLengthToUse);

        BlobOutputStream blobOutputStream = blobRef.openWriteNew(blobLengthToUse);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);

        blobOutputStream = blobRef.openWriteNew(blobLengthToUse);
        inputStream = new ByteArrayInputStream(buffer);
        blobOutputStream.write(inputStream, 512);

        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        blobOutputStream.write(inputStream, 3 * 512);

        blobOutputStream.close();

        byte[] result = new byte[blobLengthToUse];
        blobRef.downloadToByteArray(result, 0);

        int i = 0;
        for (; i < 4 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (; i < 8 * 512; i++) {
            assertEquals(0, result[i]);
        }
    }

    @Test
    public void testOpenOutputStreamNoArgs() throws URISyntaxException, StorageException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudPageBlob pageBlob = this.container.getPageBlobReference(blobName);

        try {
            pageBlob.openWriteExisting();
        }
        catch (StorageException ex) {
            assertEquals("The specified blob does not exist.", ex.getMessage());
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }

        pageBlob.openWriteNew(1024);
        pageBlob.openWriteExisting();

        CloudPageBlob pageBlob2 = this.container.getPageBlobReference(blobName);
        pageBlob2.downloadAttributes();
        assertEquals(1024, pageBlob2.getProperties().getLength());
        assertEquals(BlobType.PAGE_BLOB, pageBlob2.getProperties().getBlobType());
    }
    
    @Test
    public void testCopyPageBlobIncrementalSnapshot() throws URISyntaxException, StorageException, IOException, InvalidKeyException, InterruptedException {
        for (int i = 0; i < 4; i++) {
            testCopyPageBlobIncrementalSnapshotImpl(i);
        }
    }

    private void testCopyPageBlobIncrementalSnapshotImpl(int overload) throws URISyntaxException, StorageException, IOException, InvalidKeyException, InterruptedException {
        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(BlobTestHelper.generateRandomBlobNameWithPrefix("copy"));

        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.SNAPSHOT, sasSnapshotBlob.snapshotID);
        URI sourceUri = TestHelper.defiddler(builder.addToURI(sasSnapshotBlob.getTransformedAddress(null).getPrimaryUri()));

        String copyId = null;
        if (overload == 0) {
            copyId = copy.startIncrementalCopy(BlobTestHelper.defiddler(sasSnapshotBlob));
        }
        else if (overload == 1) {
            copyId = copy.startIncrementalCopy(BlobTestHelper.defiddler(sasSnapshotBlob), null, null, null);
        }
        else if (overload == 2) {
            copyId = copy.startIncrementalCopy(sourceUri);
        }
        else {
            copyId = copy.startIncrementalCopy(sourceUri, null, null, null);
        }

        BlobTestHelper.waitForCopy(copy);

        assertEquals(BlobType.PAGE_BLOB, copy.getProperties().getBlobType());
        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(sourceUri.getSchemeSpecificPart(), copy.getCopyState().getSource().getSchemeSpecificPart());
        assertTrue(buffer.length == copy.getCopyState().getTotalBytes());
        assertTrue(buffer.length == copy.getCopyState().getBytesCopied());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.properties.isIncrementalCopy());
        assertNotNull(copy.properties.getCopyState().getCopyDestinationSnapshotID());
        assertNotNull(copy.getCopyState().getCompletionTime());
    }

    @Test
    public void testEightTBBlob() throws StorageException, URISyntaxException, IOException {
        CloudPageBlob blob = this.container.getPageBlobReference("blob1");
        CloudPageBlob blob2 = this.container.getPageBlobReference("blob1");

        long eightTb = 8L * 1024L * 1024L * 1024L * 1024L;
        blob.create(eightTb);
        assertEquals(eightTb, blob.getProperties().getLength());

        blob2.downloadAttributes();
        assertEquals(eightTb, blob2.getProperties().getLength());

        for (ListBlobItem listBlob : this.container.listBlobs()) {
            CloudPageBlob listPageBlob = (CloudPageBlob)listBlob;
            assertEquals(eightTb, listPageBlob.getProperties().getLength());
        }

        CloudPageBlob blob3 = this.container.getPageBlobReference("blob3");
        blob3.create(1024);
        blob3.resize(eightTb);

        final Random randGenerator = new Random();
        final byte[] buffer = new byte[1024];
        randGenerator.nextBytes(buffer);
        blob.uploadPages(new ByteArrayInputStream(buffer), eightTb - 512L, 512L);

        ArrayList<PageRange> ranges = blob.downloadPageRanges();
        assertEquals(1, ranges.size());
        assertEquals(eightTb - 512L, ranges.get(0).getStartOffset());
        assertEquals(eightTb - 1L, ranges.get(0).getEndOffset());
    }

    @Test
    @Category(PremiumBlobTests.class)
    public void testCloudPageBlobSetPremiumBlobTierOnCreate() throws URISyntaxException, StorageException, IOException {
        CloudBlobContainer container =  BlobTestHelper.getRandomPremiumBlobContainerReference();
        try {
            container.create();
            String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");

            // Test create API
            CloudPageBlob blob = container.getPageBlobReference(blobName);
            assertNull(blob.getProperties().isBlobTierInferred());
            blob.create(1024, PremiumPageBlobTier.P4, null, null, null);
            assertEquals(PremiumPageBlobTier.P4, blob.getProperties().getPremiumPageBlobTier());
            assertFalse(blob.getProperties().isBlobTierInferred());
            assertNull(blob.getProperties().getStandardBlobTier());
            assertNull(blob.getProperties().getRehydrationStatus());

            CloudPageBlob blob2 = container.getPageBlobReference(blobName);
            blob2.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P4, blob2.getProperties().getPremiumPageBlobTier());
            assertNull(blob2.getProperties().isBlobTierInferred());
            assertNull(blob2.getProperties().getStandardBlobTier());
            assertNull(blob2.getProperties().getRehydrationStatus());

            // Test upload from byte array API
            byte[] buffer = BlobTestHelper.getRandomBuffer(1024);
            CloudPageBlob blob3 = container.getPageBlobReference("blob3");
            blob3.uploadFromByteArray(buffer, 0, 1024, PremiumPageBlobTier.P6, null, null, null);
            assertEquals(PremiumPageBlobTier.P6, blob3.getProperties().getPremiumPageBlobTier());
            assertFalse(blob3.getProperties().isBlobTierInferred());
            assertNull(blob3.getProperties().getStandardBlobTier());
            assertNull(blob3.getProperties().getRehydrationStatus());

            CloudPageBlob blob3Ref = container.getPageBlobReference("blob3");
            blob3Ref.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P6, blob3Ref.getProperties().getPremiumPageBlobTier());
            assertNull(blob3Ref.getProperties().isBlobTierInferred());

            // Test upload from stream API
            ByteArrayInputStream srcStream = new ByteArrayInputStream(buffer);
            CloudPageBlob blob4 = container.getPageBlobReference("blob4");
            blob4.upload(srcStream, 1024, PremiumPageBlobTier.P10, null, null, null);
            assertEquals(PremiumPageBlobTier.P10, blob4.getProperties().getPremiumPageBlobTier());
            assertFalse(blob4.getProperties().isBlobTierInferred());
            assertNull(blob4.getProperties().getStandardBlobTier());
            assertNull(blob4.getProperties().getRehydrationStatus());

            CloudPageBlob blob4Ref = container.getPageBlobReference("blob4");
            blob4Ref.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P10, blob4Ref.getProperties().getPremiumPageBlobTier());
            assertNull(blob4Ref.getProperties().isBlobTierInferred());

            // Test upload from file API
            File sourceFile = File.createTempFile("sourceFile", ".tmp");
            File destinationFile = new File(sourceFile.getParentFile(),
                    "destinationFile.tmp");
            FileOutputStream fos = new FileOutputStream(sourceFile);
            fos.write(buffer);
            fos.close();
            CloudPageBlob blob5 = container.getPageBlobReference("blob5");
            blob5.uploadFromFile(sourceFile.getAbsolutePath(), PremiumPageBlobTier.P20, null, null, null);
            assertEquals(PremiumPageBlobTier.P20, blob5.getProperties().getPremiumPageBlobTier());
            assertFalse(blob5.getProperties().isBlobTierInferred());
            assertNull(blob5.getProperties().getStandardBlobTier());
            assertNull(blob5.getProperties().getRehydrationStatus());

            CloudPageBlob blob5Ref = container.getPageBlobReference("blob5");
            blob5Ref.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P20, blob5Ref.getProperties().getPremiumPageBlobTier());
            assertNull(blob5Ref.getProperties().isBlobTierInferred());
        }
        finally {
            container.deleteIfExists();
        }
    }

    @Test
    @Category(PremiumBlobTests.class)
    public void testCloudPageBlobSetBlobTier() throws URISyntaxException, StorageException {
        CloudBlobContainer container =  BlobTestHelper.getRandomPremiumBlobContainerReference();
        try {
            container.create();
            String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
            CloudPageBlob blob = container.getPageBlobReference(blobName);
            blob.create(1024);
            assertNull(blob.getProperties().isBlobTierInferred());
            blob.downloadAttributes();
            assertTrue(blob.getProperties().isBlobTierInferred());
            assertEquals(PremiumPageBlobTier.P10, blob.getProperties().getPremiumPageBlobTier());

            blob.uploadPremiumPageBlobTier(PremiumPageBlobTier.P40);
            assertEquals(PremiumPageBlobTier.P40, blob.properties.getPremiumPageBlobTier());
            assertFalse(blob.getProperties().isBlobTierInferred());
            assertNull(blob.getProperties().getStandardBlobTier());
            assertNull(blob.getProperties().getRehydrationStatus());

            CloudPageBlob blob2 = container.getPageBlobReference(blobName);
            blob2.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P40, blob2.properties.getPremiumPageBlobTier());
            assertNull(blob2.getProperties().isBlobTierInferred());

            boolean pageBlobWithTierFound = false;
            for (ListBlobItem blobItem : container.listBlobs()) {
                CloudPageBlob blob3 = (CloudPageBlob) blobItem;

                if (blob.getName().equals(blobName) && !pageBlobWithTierFound) {
                    // Check that the blob is found exactly once
                    assertEquals(PremiumPageBlobTier.P40, blob3.properties.getPremiumPageBlobTier());
                    assertNull(blob3.getProperties().isBlobTierInferred());
                    assertNull(blob3.getProperties().getStandardBlobTier());
                    assertNull(blob3.getProperties().getRehydrationStatus());
                    pageBlobWithTierFound = true;
                } else if (blob.getName().equals(blobName)) {
                    fail("Page blob found twice");
                }
            }

            assertTrue(pageBlobWithTierFound);

            try
            {
                CloudPageBlob blob4 = container.getPageBlobReference("blob4");
                blob4.create(256 * (long)Constants.GB);
                blob4.uploadPremiumPageBlobTier(PremiumPageBlobTier.P6);
                fail("Expected failure when setting blob tier size to be less than content length");
            }
            catch (StorageException e)
            {
                assertEquals("Specified blob tier size limit cannot be less than content length.", e.getMessage());
            }

            try
            {
                blob2.uploadPremiumPageBlobTier(PremiumPageBlobTier.P4);
                fail("Expected failure when attempted to set the tier to a lower value than previously");
            }
            catch (StorageException e)
            {
                assertEquals("A higher blob tier has already been explicitly set.", e.getMessage());
            }
        }
        finally {
            container.deleteIfExists();
        }
    }

    @Test
    @Category(PremiumBlobTests.class)
    public void testCloudPageBlobSetBlobTierOnCopy() throws URISyntaxException, StorageException, InterruptedException {
        CloudBlobContainer container =  BlobTestHelper.getRandomPremiumBlobContainerReference();
        try {
            container.create();
            CloudPageBlob source = container.getPageBlobReference("source");
            source.create(1024, PremiumPageBlobTier.P10, null, null, null);

            // copy to larger disk
            CloudPageBlob copy = container.getPageBlobReference("copy");
            String copyId = copy.startCopy(TestHelper.defiddler(source.getUri()), PremiumPageBlobTier.P30, null, null, null, null);
            assertEquals(BlobType.PAGE_BLOB, copy.getProperties().getBlobType());
            assertEquals(PremiumPageBlobTier.P30, copy.getProperties().getPremiumPageBlobTier());
            assertEquals(PremiumPageBlobTier.P10, source.getProperties().getPremiumPageBlobTier());
            assertFalse(source.getProperties().isBlobTierInferred());
            assertFalse(copy.getProperties().isBlobTierInferred());
            assertNull(source.getProperties().getStandardBlobTier());
            assertNull(source.getProperties().getRehydrationStatus());
            assertNull(copy.getProperties().getStandardBlobTier());
            assertNull(copy.getProperties().getRehydrationStatus());
            BlobTestHelper.waitForCopy(copy);

            CloudPageBlob copyRef = container.getPageBlobReference("copy");
            copyRef.downloadAttributes();
            assertEquals(PremiumPageBlobTier.P30, copyRef.getProperties().getPremiumPageBlobTier());
            assertNull(copyRef.getProperties().isBlobTierInferred());

            // copy where source does not have a tier
            CloudPageBlob source2 = container.getPageBlobReference("source2");
            source2.create(1024);

            CloudPageBlob copy3 = container.getPageBlobReference("copy3");
            String copyId3 = copy3.startCopy(TestHelper.defiddler(source2.getUri()), PremiumPageBlobTier.P60, null ,null ,null, null);
            assertEquals(BlobType.PAGE_BLOB, copy3.getProperties().getBlobType());
            assertEquals(PremiumPageBlobTier.P60, copy3.getProperties().getPremiumPageBlobTier());
            assertNull(source2.getProperties().getPremiumPageBlobTier());
            assertNull(source2.getProperties().isBlobTierInferred());
            assertFalse(copy3.getProperties().isBlobTierInferred());
            assertNull(source2.getProperties().getStandardBlobTier());
            assertNull(source2.getProperties().getRehydrationStatus());
            assertNull(copy3.getProperties().getStandardBlobTier());
            assertNull(copy3.getProperties().getRehydrationStatus());
        }
        finally {
            container.deleteIfExists();
        }
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class LeaseTests {

    protected CloudBlobContainer container;

    @Before
    public void leaseTestMethodSetUp() throws StorageException, URISyntaxException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();
    }

    @After
    public void leaseTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    @Test
    public void testContainerLeaseInvalidParams() throws StorageException, URISyntaxException {
        try {
            this.container.acquireLease(100, null);   
        } catch(StorageException ex) {
            assertEquals("The value of the parameter 'leaseTimeInSeconds' should be between 15 and 60.", 
                    ex.getMessage());
        }
        
        try {
            this.container.breakLease(100);   
        } catch(StorageException ex) {
            assertEquals("The value of the parameter 'breakPeriodInSeconds' should be between 0 and 60.", 
                    ex.getMessage());
        }
    }

    @Test
    public void testContainerAcquireLease() throws StorageException, URISyntaxException {
        CloudBlobContainer leaseContainer1 = BlobTestHelper.getRandomContainerReference();
        leaseContainer1.create();
        String proposedLeaseId1 = UUID.randomUUID().toString();

        CloudBlobContainer leaseContainer2 = BlobTestHelper.getRandomContainerReference();
        leaseContainer2.create();
        String proposedLeaseId2 = UUID.randomUUID().toString();

        try {
            // 15 sec
            OperationContext operationContext1 = new OperationContext();
            leaseContainer1.acquireLease(15, proposedLeaseId1, null /*access condition*/,
                    null/* BlobRequestOptions */, operationContext1);
            assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED);

            // infinite
            String leaseId1;
            String leaseId2;
            OperationContext operationContext2 = new OperationContext();
            leaseId1 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2,
                    null /*access condition*/, null/* BlobRequestOptions */, operationContext2);
            assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED);

            leaseId2 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2);
            assertEquals(leaseId1, leaseId2);
        }
        finally {
            // cleanup
            AccessCondition condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId1);
            leaseContainer1.releaseLease(condition);
            leaseContainer1.deleteIfExists();

            condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId2);
            leaseContainer2.releaseLease(condition);
            leaseContainer2.deleteIfExists();
        }
    }

    @Test
    public void testContainerReleaseLease() throws StorageException {
        // 15 sec
        String proposedLeaseId = UUID.randomUUID().toString();
        String leaseId = this.container.acquireLease(15, proposedLeaseId);
        AccessCondition condition = new AccessCondition();
        condition.setLeaseID(leaseId);
        OperationContext operationContext1 = new OperationContext();
        this.container.releaseLease(condition, null/* BlobRequestOptions */, operationContext1);
        assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK);

        // infinite
        leaseId = this.container.acquireLease();
        condition = new AccessCondition();
        condition.setLeaseID(leaseId);
        OperationContext operationContext2 = new OperationContext();
        this.container.releaseLease(condition, null/* BlobRequestOptions */, operationContext2);
        assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK);
    }

    @Test
    @Category(SlowTests.class)
    public void testContainerBreakLease() throws StorageException, InterruptedException {
        String proposedLeaseId = UUID.randomUUID().toString();
        try {
            // 5 sec
            this.container.acquireLease(15, proposedLeaseId);
            AccessCondition condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            OperationContext operationContext1 = new OperationContext();
            this.container.breakLease(0, condition, null/* BlobRequestOptions */, operationContext1);
            assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED);
            Thread.sleep(15 * 1000);

            // infinite
            proposedLeaseId = this.container.acquireLease();
            condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            OperationContext operationContext2 = new OperationContext();
            this.container.breakLease(0, condition, null/* BlobRequestOptions */, operationContext2);
            assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED);
        }
        finally {
            // cleanup
            AccessCondition condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            this.container.releaseLease(condition);
        }
    }

    @Test
    public void testContainerRenewLeaseTest() throws StorageException{
        String proposedLeaseId = UUID.randomUUID().toString();
        try {
            // 5 sec
            this.container.acquireLease(15, proposedLeaseId);
            AccessCondition condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            OperationContext operationContext1 = new OperationContext();
            this.container.renewLease(condition, null/* BlobRequestOptions */, operationContext1);
            assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK);
            this.container.releaseLease(condition);

            // infinite
            proposedLeaseId = this.container.acquireLease();
            condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            OperationContext operationContext2 = new OperationContext();
            this.container.renewLease(condition, null/* BlobRequestOptions */, operationContext2);
            assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK);
        }
        finally {
            // cleanup
            AccessCondition condition = new AccessCondition();
            condition.setLeaseID(proposedLeaseId);
            this.container.releaseLease(condition);
        }
    }

    @Test
    public void testContainerChangeLeaseTest() throws StorageException {
        // Get Lease 
        String leaseID1;
        String leaseID2;

        OperationContext operationContext = new OperationContext();
        leaseID1 = this.container.acquireLease(null /* infinite lease */, null /*proposed lease id */,
                null /*access condition*/, null/* BlobRequestOptions */, operationContext);
        assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED);

        //Change leased state with idempotent change
        final String proposedLeaseId = UUID.randomUUID().toString();
        leaseID2 = this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID1));
        leaseID2 = this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID1));

        //Change lease state with same proposed ID but different lease ID
        leaseID2 = this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID2));
        leaseID2 = this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID1));

        //Change lease (wrong lease ID specified)
        final String proposedLeaseId2 = UUID.randomUUID().toString();
        leaseID2 = this.container.changeLease(proposedLeaseId2, AccessCondition.generateLeaseCondition(leaseID2));
        try {
            this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID1));
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        // Change released lease
        this.container.releaseLease(AccessCondition.generateLeaseCondition(leaseID2));
        try {
            this.container.changeLease(leaseID2, AccessCondition.generateLeaseCondition(leaseID2));
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        // Change a breaking lease (same ID)
        leaseID1 = this.container.acquireLease(null /* infinite lease */, null /*proposed lease id */,
                null /*access condition*/, null/* BlobRequestOptions */, operationContext);
        this.container.breakLease(60);
        try {
            this.container.changeLease(proposedLeaseId2, AccessCondition.generateLeaseCondition(leaseID1));
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        // Change broken lease
        this.container.breakLease(0);
        try {
            this.container.changeLease(proposedLeaseId, AccessCondition.generateLeaseCondition(leaseID1));
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        // Change broken lease (to previous lease)
        leaseID1 = this.container.acquireLease(null /* infinite lease */, null /*proposed lease id */,
                null /*access condition*/, null/* BlobRequestOptions */, operationContext);
        this.container.breakLease(0);
        try {
            this.container.changeLease(leaseID1, AccessCondition.generateLeaseCondition(leaseID1));
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }
    }

    @Test
    public void testBlobLeaseAcquireAndRelease() throws URISyntaxException, StorageException, IOException {
        final int length = 128;
        final CloudBlob blobRef = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 128, null);

        // Get Lease 
        OperationContext operationContext = new OperationContext();
        final String leaseID = blobRef.acquireLease(15, null /*proposed lease id */, null /*access condition*/,
                null/* BlobRequestOptions */, operationContext);
        final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID);
        assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED);

        tryUploadWithBadLease(length, blobRef, null, StorageErrorCodeStrings.LEASE_ID_MISSING);

        // Try to upload with lease
        blobRef.upload(BlobTestHelper.getRandomDataStream(length), -1, leaseCondition, null, null);

        // Release lease
        blobRef.releaseLease(leaseCondition);

        // now upload with no lease specified.
        blobRef.upload(BlobTestHelper.getRandomDataStream(length), -1);
    }

    @Test
    public void testBlobLeaseChange() throws StorageException, IOException, URISyntaxException {
        final int length = 128;
        final CloudBlob blobRef = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 128, null);

        // Get Lease 
        OperationContext operationContext = new OperationContext();
        final String leaseID1 = blobRef.acquireLease(15, null /*proposed lease id */, null /*access condition*/,
                null/* BlobRequestOptions */, operationContext);
        final AccessCondition leaseCondition1 = AccessCondition.generateLeaseCondition(leaseID1);
        assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED);

        final String leaseID2 = UUID.randomUUID().toString();
        final AccessCondition leaseCondition2 = AccessCondition.generateLeaseCondition(leaseID2);

        // Try to upload without lease
        tryUploadWithBadLease(length, blobRef, null, StorageErrorCodeStrings.LEASE_ID_MISSING);

        // Try to upload with incorrect lease
        tryUploadWithBadLease(length, blobRef, leaseCondition2,
                StorageErrorCodeStrings.LEASE_ID_MISMATCH_WITH_BLOB_OPERATION);

        // Try to upload with correct lease
        blobRef.upload(BlobTestHelper.getRandomDataStream(length), -1, leaseCondition1, null, null);

        // Fail to change the lease with a bad accessCondition
        try {
            blobRef.changeLease(leaseID2, leaseCondition2);
            fail("Did not throw expected exception.");
        }
        catch (final StorageException ex) {
            assertEquals(ex.getHttpStatusCode(), 409);
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.LEASE_ID_MISMATCH_WITH_LEASE_OPERATION);
        }

        // Change the lease
        blobRef.changeLease(leaseID2, leaseCondition1);

        // Try to upload without lease
        tryUploadWithBadLease(length, blobRef, null, StorageErrorCodeStrings.LEASE_ID_MISSING);

        // Try to upload with incorrect lease
        tryUploadWithBadLease(length, blobRef, leaseCondition1,
                StorageErrorCodeStrings.LEASE_ID_MISMATCH_WITH_BLOB_OPERATION);

        // Try to upload with correct lease
        blobRef.upload(BlobTestHelper.getRandomDataStream(length), -1, leaseCondition2, null, null);
    }

    /**
     * Try to upload with a bad lease
     * 
     * @param length
     * @param blobRef
     * @throws IOException
     */
    private void tryUploadWithBadLease(final int length, final CloudBlob blobRef, final AccessCondition leaseCondition,
            final String expectedError) throws IOException {
        try {
            blobRef.upload(BlobTestHelper.getRandomDataStream(length), -1, leaseCondition, null, null);
            fail("Did not throw expected exception");
        }
        catch (final StorageException ex) {
            assertEquals(ex.getHttpStatusCode(), 412);
            assertEquals(ex.getErrorCode(), expectedError);
        }
    }

    @Test
    public void testBlobLeaseBreak() throws StorageException, IOException, URISyntaxException {
        final CloudBlob blobRef = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 128, null);

        // Get Lease
        String leaseID = blobRef.acquireLease();

        OperationContext operationContext = new OperationContext();
        final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID);
        blobRef.breakLease(0, leaseCondition, null/* BlobRequestOptions */, operationContext);
        assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED);
    }

    @Test
    @Category(SlowTests.class)
    public void testBlobLeaseRenew() throws StorageException, IOException, InterruptedException, URISyntaxException {
        final CloudBlob blobRef = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 128, null);

        // Get Lease
        final String leaseID = blobRef.acquireLease(15, null);
        Thread.sleep(1000);

        AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID);
        OperationContext operationContext = new OperationContext();
        blobRef.renewLease(leaseCondition, null/* BlobRequestOptions */, operationContext);
        assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK);
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class SasTests {

    protected CloudBlobContainer container;
    protected CloudBlockBlob blob;

    @Before
    public void blobSasTestMethodSetUp() throws URISyntaxException, StorageException, IOException {
        this.container = BlobTestHelper.getRandomContainerReference();
        this.container.create();

        this.blob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "test", 100, null);
    }

    @After
    public void blobSasTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    @Test
    public void testApiVersion() throws InvalidKeyException, StorageException, URISyntaxException {
        SharedAccessBlobPolicy sp1 = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                        SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 3600);
        String sas = this.blob.generateSharedAccessSignature(sp1, null);

        // should not be appended before signing
        assertEquals(-1, sas.indexOf(Constants.QueryConstants.API_VERSION));

        OperationContext ctx = new OperationContext();
        ctx.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() {

            @Override
            public void eventOccurred(ResponseReceivedEvent eventArg) {
                // should be appended after signing
                HttpURLConnection conn = (HttpURLConnection) eventArg.getConnectionObject();
                assertTrue(conn.getURL().toString().indexOf(Constants.QueryConstants.API_VERSION) != -1);
            }
        });

        CloudBlockBlob sasBlob = new CloudBlockBlob(new URI(this.blob.getUri().toString() + "?" + sas));
        sasBlob.uploadMetadata(null, null, ctx);
    }

    @Test
    @Category(SecondaryTests.class)
    public void testIpAcl()
            throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException, UnknownHostException {
        
        // Generate policies
        SharedAccessBlobPolicy sp = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 3600);
        IPRange range1 = new IPRange("0.0.0.0", "255.255.255.255");
        IPRange range2 = new IPRange("0.0.0.0");
        
        // Ensure access attempt from invalid IP fails
        IPRange sourceIP = null;
        try {
            String containerSasNone = this.container.generateSharedAccessSignature(sp, null, range2, null);
            CloudBlobContainer noneContainer =
                    new CloudBlobContainer(PathUtility.addToQuery(this.container.getUri(), containerSasNone));
    
            assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                    noneContainer.getServiceClient().getCredentials().getClass().toString());
    
            CloudBlockBlob noneBlob = noneContainer.getBlockBlobReference(this.blob.getName());
            noneBlob.download(new ByteArrayOutputStream());
            fail();
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            
            final String[] words = ex.getMessage().split(" ");
            // final word
            String lastWord = words[words.length - 1];
            // strip trailing period
            lastWord = lastWord.substring(0, lastWord.length() - 1);
            
            sourceIP = new IPRange(lastWord);
        }
        
        // Ensure access attempt from the single allowed IP succeeds
        String containerSasOne = this.container.generateSharedAccessSignature(sp, null, sourceIP, null);
        CloudBlobContainer oneContainer =
                new CloudBlobContainer(PathUtility.addToQuery(this.container.getUri(), containerSasOne));

        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                oneContainer.getServiceClient().getCredentials().getClass().toString());

        CloudBlockBlob oneBlob = oneContainer.getBlockBlobReference(this.blob.getName());
        oneBlob.download(new ByteArrayOutputStream());

        // Ensure access attempt from one of many valid IPs succeeds
        String containerSasAll = this.container.generateSharedAccessSignature(sp, null, range1, null);
        CloudBlobContainer allContainer =
                new CloudBlobContainer(PathUtility.addToQuery(this.container.getUri(), containerSasAll));

        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                allContainer.getServiceClient().getCredentials().getClass().toString());

        CloudBlockBlob allBlob = allContainer.getBlockBlobReference(this.blob.getName());
        allBlob.download(new ByteArrayOutputStream());
    }

    @Test
    @Category(SecondaryTests.class)
    public void testProtocolRestrictions()
            throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException {
        
        // Generate policy
        SharedAccessBlobPolicy sp = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 3600);
        
        // Generate container SAS and URI
        String containerSasHttps = this.container.generateSharedAccessSignature(
                sp, null, null, SharedAccessProtocols.HTTPS_ONLY);
        String containerSasHttp = this.container.generateSharedAccessSignature(
                sp, null, null, SharedAccessProtocols.HTTPS_HTTP);
        final URI httpUri = new URI("http", this.container.getUri().getAuthority(), this.container.getUri().getPath(), 
        		this.container.getUri().getQuery(), null);
        final URI httpsUri = new URI("https", this.container.getUri().getAuthority(), this.container.getUri().getPath(), 
        		this.container.getUri().getQuery(), null);
        
        // Ensure attempt from http fails against HTTPS_ONLY
        try {
            CloudBlobContainer httpContainer = new CloudBlobContainer(
                    PathUtility.addToQuery(httpUri, containerSasHttps));
            assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                    httpContainer.getServiceClient().getCredentials().getClass().toString());
            CloudBlockBlob httpBlob = httpContainer.getBlockBlobReference(this.blob.getName());
            httpBlob.download(new ByteArrayOutputStream());
            
            fail();
        }
        catch (StorageException ex) {
            assertEquals(Constants.HeaderConstants.HTTP_UNUSED_306, ex.getHttpStatusCode());
            assertEquals(SR.CANNOT_TRANSFORM_NON_HTTPS_URI_WITH_HTTPS_ONLY_CREDENTIALS, ex.getMessage());
        }

        // Ensure attempt from https succeeds against HTTPS_ONLY
        CloudBlobContainer httpsContainer = new CloudBlobContainer(
                PathUtility.addToQuery(httpsUri, containerSasHttps));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpsContainer.getServiceClient().getCredentials().getClass().toString());
        CloudBlockBlob httpsBlob = httpsContainer.getBlockBlobReference(this.blob.getName());
        httpsBlob.download(new ByteArrayOutputStream());
        
        //Ensure attempts from both https and http succeed against HTTPS_HTTP
        CloudBlobContainer httpContainer = new CloudBlobContainer(
                PathUtility.addToQuery(httpUri, containerSasHttp));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpContainer.getServiceClient().getCredentials().getClass().toString());
        CloudBlockBlob httpBlob = httpContainer.getBlockBlobReference(this.blob.getName());
        httpBlob.download(new ByteArrayOutputStream());
        
        httpsContainer = new CloudBlobContainer(
                PathUtility.addToQuery(httpsUri, containerSasHttp));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpsContainer.getServiceClient().getCredentials().getClass().toString());
        httpsBlob = httpsContainer.getBlockBlobReference(this.blob.getName());
        httpsBlob.download(new ByteArrayOutputStream());
    }

    @Test
    @Category ({ SecondaryTests.class, SlowTests.class })
    public void testContainerSaS() throws IllegalArgumentException, StorageException, URISyntaxException,
            InvalidKeyException, InterruptedException {
        SharedAccessBlobPolicy sp1 = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                        SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 3600);
        SharedAccessBlobPolicy sp2 = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 3600);
        BlobContainerPermissions perms = new BlobContainerPermissions();

        perms.getSharedAccessPolicies().put("full", sp1);
        perms.getSharedAccessPolicies().put("readlist", sp2);
        this.container.uploadPermissions(perms);
        Thread.sleep(30000);

        String containerReadListSas = this.container.generateSharedAccessSignature(sp2, null);
        CloudBlobContainer readListContainer = new CloudBlobContainer(PathUtility.addToQuery(this.container.getUri(),
                containerReadListSas));

        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), readListContainer.getServiceClient()
                .getCredentials().getClass().toString());

        CloudBlockBlob blobFromSasContainer = readListContainer.getBlockBlobReference(this.blob.getName());
        blobFromSasContainer.download(new ByteArrayOutputStream());

        // do not give the client and check that the new container's client has the correct perms
        CloudBlobContainer containerFromUri = new CloudBlobContainer(PathUtility.addToQuery(
                readListContainer.getStorageUri(), this.container.generateSharedAccessSignature(null, "readlist")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), containerFromUri.getServiceClient()
                .getCredentials().getClass().toString());
        
        // create credentials from sas
        StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
                this.container.generateSharedAccessSignature(null, "readlist"));
        CloudBlobClient bClient = new CloudBlobClient(this.container.getServiceClient().getStorageUri(), creds);

        CloudBlobContainer containerFromClient = bClient.getContainerReference(this.container.getName());
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), containerFromClient.getServiceClient()
                .getCredentials().getClass().toString());
        assertEquals(bClient, containerFromClient.getServiceClient());
    }

    @Test
    @Category(SlowTests.class)
    public void testContainerUpdateSAS() throws InvalidKeyException, StorageException, IOException, URISyntaxException,
            InterruptedException {
        //Create a policy with read/write access and get SAS.
        SharedAccessBlobPolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE), 3600);
        BlobContainerPermissions perms = new BlobContainerPermissions();

        perms.getSharedAccessPolicies().put("readwrite", policy);
        this.container.uploadPermissions(perms);
        Thread.sleep(30000);

        String sasToken = this.container.generateSharedAccessSignature(policy, null);

        CloudBlockBlob blob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB,
                "blockblob", 64, null);
        testAccess(sasToken, EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE), null,
                this.container, blob);

        //Change the policy to only read and update SAS.
        SharedAccessBlobPolicy policy2 = createSharedAccessPolicy(EnumSet.of(SharedAccessBlobPermissions.READ), 3600);
        perms = new BlobContainerPermissions();

        perms.getSharedAccessPolicies().put("read", policy2);
        this.container.uploadPermissions(perms);
        Thread.sleep(30000);

        // Extra check to make sure that we have actually updated the SAS token.
        String sasToken2 = this.container.generateSharedAccessSignature(policy2, null);
        CloudBlobContainer sasContainer = new CloudBlobContainer(PathUtility.addToQuery(this.container.getUri(),
                sasToken2));

        try {
            BlobTestHelper.uploadNewBlob(sasContainer, BlobType.BLOCK_BLOB, "blockblob", 64, null);
            Assert.fail();
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testContainerSASCombinations()
            throws StorageException, URISyntaxException, IOException, InvalidKeyException, InterruptedException {
        EnumSet<SharedAccessBlobPermissions> permissions;
        Map<Integer, CloudBlobContainer> containers = new HashMap<Integer, CloudBlobContainer>();

        try{
            for (int bits = 0x1; bits < 0x40; bits++) {
                containers.put(bits, BlobTestHelper.getRandomContainerReference());
                containers.get(bits).createIfNotExists();

                permissions = EnumSet.noneOf(SharedAccessBlobPermissions.class);
                addPermissions(permissions, bits);

                BlobContainerPermissions perms = new BlobContainerPermissions();

                perms.getSharedAccessPolicies().put("readwrite" + bits, createSharedAccessPolicy(permissions, 3600));
                containers.get(bits).uploadPermissions(perms);
            }

            Thread.sleep(30000);

            for (int bits = 0x1; bits < 0x40; bits++) {
                String sasToken = containers.get(bits).generateSharedAccessSignature(null, "readwrite" + bits);
                permissions = EnumSet.noneOf(SharedAccessBlobPermissions.class);
                addPermissions(permissions, bits);
                
                CloudAppendBlob testAppendBlob = (CloudAppendBlob) BlobTestHelper.uploadNewBlob(
                        containers.get(bits), BlobType.APPEND_BLOB, "appendblob", 64, null);
                testAccess(sasToken, permissions, null, containers.get(bits), testAppendBlob);

                CloudBlockBlob testBlockBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(
                        containers.get(bits), BlobType.BLOCK_BLOB, "blockblob", 64, null);
                testAccess(sasToken, permissions, null, containers.get(bits), testBlockBlob);

                CloudPageBlob testPageBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob(
                        containers.get(bits), BlobType.PAGE_BLOB, "pageblob", 512, null);
                testAccess(sasToken, permissions, null, containers.get(bits), testPageBlob);
            }
        }
        finally {
            for (int bits = 0x1; bits < containers.size(); bits++) {
                containers.get(bits).deleteIfExists();
            }
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testContainerPublicAccess() throws StorageException, IOException, URISyntaxException,
            InterruptedException {
        CloudBlockBlob testBlockBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.BLOCK_BLOB, "blockblob", 64, null);
        CloudPageBlob testPageBlob = (CloudPageBlob) BlobTestHelper.uploadNewBlob(
                this.container, BlobType.PAGE_BLOB, "pageblob", 512, null);

        BlobContainerPermissions permissions = new BlobContainerPermissions();

        permissions.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);
        this.container.uploadPermissions(permissions);
        Thread.sleep(35000);

        testAccess(null, EnumSet.of(SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.READ), null,
                this.container, testBlockBlob);
        testAccess(null, EnumSet.of(SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.READ), null,
                this.container, testPageBlob);

        permissions.setPublicAccess(BlobContainerPublicAccessType.BLOB);
        this.container.uploadPermissions(permissions);
        Thread.sleep(30000);
        testAccess(null, EnumSet.of(SharedAccessBlobPermissions.READ), null, this.container, testBlockBlob);
        testAccess(null, EnumSet.of(SharedAccessBlobPermissions.READ), null, this.container, testPageBlob);
    }

    @Test
    public void testAppendBlobSASCombinations()
            throws URISyntaxException, StorageException, InvalidKeyException, IOException {
        for (int bits = 0x1; bits < 0x20; bits++) {
            final EnumSet<SharedAccessBlobPermissions> permissions = EnumSet.noneOf(SharedAccessBlobPermissions.class);
            addPermissions(permissions, bits);
            testBlobAccess(this.container, BlobType.APPEND_BLOB, permissions, null);
        }
    }

    @Test
    public void testBlockBlobSASCombinations()
            throws URISyntaxException, StorageException, InvalidKeyException, IOException {
        for (int bits = 0x1; bits < 0x20; bits++) {
            final EnumSet<SharedAccessBlobPermissions> permissions = EnumSet.noneOf(SharedAccessBlobPermissions.class);
            addPermissions(permissions, bits);
            testBlobAccess(this.container, BlobType.BLOCK_BLOB, permissions, null);
        }
    }

    @Test
    public void testPageBlobSASCombinations()
            throws InvalidKeyException, StorageException, IOException, URISyntaxException {
        for (int bits = 0x1; bits < 0x20; bits++) {
            final EnumSet<SharedAccessBlobPermissions> permissions = EnumSet.noneOf(SharedAccessBlobPermissions.class);
            addPermissions(permissions, bits);
            testBlobAccess(this.container, BlobType.PAGE_BLOB, permissions, null);
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testBlobSaS() throws InvalidKeyException, IllegalArgumentException, StorageException,
            URISyntaxException, InterruptedException {
        SharedAccessBlobPolicy sp = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 3600);
        BlobContainerPermissions perms = new BlobContainerPermissions();

        perms.getSharedAccessPolicies().put("readperm", sp);
        this.container.uploadPermissions(perms);
        Thread.sleep(30000);

        CloudBlockBlob sasBlob = new CloudBlockBlob(new URI(this.blob.getUri().toString() + "?"
                + this.blob.generateSharedAccessSignature(null, "readperm")));
        sasBlob.download(new ByteArrayOutputStream());

        // do not give the client and check that the new blob's client has the correct perms
        CloudBlob blobFromUri = new CloudBlockBlob(PathUtility.addToQuery(this.blob.getStorageUri(),
                this.blob.generateSharedAccessSignature(null, "readperm")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), blobFromUri.getServiceClient()
                .getCredentials().getClass().toString());

        // create credentials from sas
        StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
                this.blob.generateSharedAccessSignature(null, "readperm"));
        CloudBlobClient bClient = new CloudBlobClient(sasBlob.getServiceClient().getStorageUri(), creds);

        CloudBlockBlob blobFromClient = bClient.getContainerReference(this.blob.getContainer().getName())
                .getBlockBlobReference(this.blob.getName());
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), blobFromClient.getServiceClient()
                .getCredentials().getClass().toString());
        assertEquals(bClient, blobFromClient.getServiceClient());
    }

    @Test
    @Category(SlowTests.class)
    public void testBlobSaSWithSharedAccessBlobHeaders() throws InvalidKeyException, IllegalArgumentException,
            StorageException, URISyntaxException,  InterruptedException {
        SharedAccessBlobPolicy sp = createSharedAccessPolicy(EnumSet.of(SharedAccessBlobPermissions.READ,
                SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.LIST), 3600);
        BlobContainerPermissions perms = new BlobContainerPermissions();

        perms.getSharedAccessPolicies().put("readperm", sp);
        this.container.uploadPermissions(perms);
        Thread.sleep(30000);

        SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders();
        headers.setCacheControl("no-cache");
        headers.setContentDisposition("attachment; filename=\"fname.ext\"");
        headers.setContentEncoding("gzip");
        headers.setContentLanguage("da");
        headers.setContentType("text/html; charset=utf-8");

        CloudBlockBlob sasBlob = new CloudBlockBlob(new URI(this.blob.getUri().toString() + "?"
                + this.blob.generateSharedAccessSignature(null, headers, "readperm")));
        OperationContext context = new OperationContext();

        context.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                assertEquals("no-cache", connection.getHeaderField(Constants.HeaderConstants.CACHE_CONTROL));
                assertEquals("attachment; filename=\"fname.ext\"",
                        connection.getHeaderField(Constants.HeaderConstants.CONTENT_DISPOSITION));
                assertEquals("gzip", connection.getHeaderField(Constants.HeaderConstants.CONTENT_ENCODING));
                assertEquals("da", connection.getHeaderField(Constants.HeaderConstants.CONTENT_LANGUAGE));
                assertEquals("text/html; charset=utf-8",
                        connection.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE));
            }
        });

        sasBlob.download(new ByteArrayOutputStream(), null, null, context);
    }

    @Test
    public void testAppendBlobCopyWithSasAndSnapshot()
            throws URISyntaxException, StorageException, InterruptedException, IOException, InvalidKeyException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudAppendBlob source = this.container.getAppendBlobReference(blobName);
        source.createOrReplace();
        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        SharedAccessBlobPolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                      SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 5000);

        CloudAppendBlob copy = this.container.getAppendBlobReference("copy");
        String sasToken = copy.generateSharedAccessSignature(policy, null);
        CloudAppendBlob copySas = new CloudAppendBlob(new URI(copy.getUri().toString() + "?" + sasToken));

        // Generate account SAS for the source
        // Cannot generate a SAS directly on a snapshot and the SAS for the destination is only for the destination
        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(policy.getSharedAccessExpiryTime());
        final CloudBlobClient sasClient = TestHelper.createCloudBlobClient(accountPolicy, false);

        CloudAppendBlob snapshot = (CloudAppendBlob) source.createSnapshot();
        CloudAppendBlob sasBlob = (CloudAppendBlob) sasClient.getContainerReference(container.getName())
                .getBlobReferenceFromServer(snapshot.getName(), snapshot.snapshotID, null, null, null);
        sasBlob.exists();

        String copyId = copySas.startCopy(BlobTestHelper.defiddler(sasBlob));
        BlobTestHelper.waitForCopy(copySas);
        
        copySas.downloadAttributes();
        BlobProperties prop1 = copySas.getProperties();
        BlobProperties prop2 = sasBlob.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copySas.getMetadata().get("Test"));
        assertEquals(copyId, copySas.getCopyState().getCopyId());

        snapshot.delete();
        source.delete();
        copySas.delete();
    }

    @Test
    public void testBlockBlobCopyWithSasAndSnapshot()
            throws URISyntaxException, StorageException, InterruptedException, IOException, InvalidKeyException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudBlockBlob source = this.container.getBlockBlobReference(blobName);
        String data = "String data";
        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        SharedAccessBlobPolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                      SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 5000);

        CloudBlockBlob copy = this.container.getBlockBlobReference("copy");
        String sasToken = copy.generateSharedAccessSignature(policy, null);
        CloudBlockBlob copySas = new CloudBlockBlob(new URI(copy.getUri().toString() + "?" + sasToken));
        
        // Generate account SAS for the source
        // Cannot generate a SAS directly on a snapshot and the SAS for the destination is only for the destination
        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(policy.getSharedAccessExpiryTime());
        final CloudBlobClient sasClient = TestHelper.createCloudBlobClient(accountPolicy, false);

        CloudBlockBlob snapshot = (CloudBlockBlob) source.createSnapshot();
        CloudBlockBlob sasBlob = (CloudBlockBlob) sasClient.getContainerReference(container.getName())
                .getBlobReferenceFromServer(snapshot.getName(), snapshot.snapshotID, null, null, null);
        sasBlob.exists();

        String copyId = copySas.startCopy(BlobTestHelper.defiddler(sasBlob));
        BlobTestHelper.waitForCopy(copySas);
        
        copySas.downloadAttributes();
        BlobProperties prop1 = copySas.getProperties();
        BlobProperties prop2 = sasBlob.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copySas.getMetadata().get("Test"));
        assertEquals(copyId, copySas.getCopyState().getCopyId());

        snapshot.delete();
        source.delete();
        copySas.delete();
    }

    @Test
    public void testPageBlobCopyWithSasAndSnapshot()
            throws URISyntaxException, StorageException, InterruptedException, IOException, InvalidKeyException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudPageBlob source = this.container.getPageBlobReference(blobName);
        source.create(1024);
        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        SharedAccessBlobPolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                      SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 5000);

        CloudPageBlob copy = this.container.getPageBlobReference("copy");
        String sasToken = copy.generateSharedAccessSignature(policy, null);
        CloudPageBlob copySas = new CloudPageBlob(new URI(copy.getUri().toString() + "?" + sasToken));
        
        // Generate account SAS for the source
        // Cannot generate a SAS directly on a snapshot and the SAS for the destination is only for the destination
        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(policy.getSharedAccessExpiryTime());
        final CloudBlobClient sasClient = TestHelper.createCloudBlobClient(accountPolicy, false);

        CloudPageBlob snapshot = (CloudPageBlob) source.createSnapshot();
        CloudPageBlob sasBlob = (CloudPageBlob) sasClient.getContainerReference(container.getName())
                .getBlobReferenceFromServer(snapshot.getName(), snapshot.snapshotID, null, null, null);
        sasBlob.exists();

        String copyId = copySas.startCopy(BlobTestHelper.defiddler(sasBlob));
        BlobTestHelper.waitForCopy(copySas);
        
        copySas.downloadAttributes();
        BlobProperties prop1 = copySas.getProperties();
        BlobProperties prop2 = sasBlob.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copySas.getMetadata().get("Test"));
        assertEquals(copyId, copySas.getCopyState().getCopyId());

        snapshot.delete();
        source.delete();
        copySas.delete();
    }

    private final static SharedAccessBlobPolicy createSharedAccessPolicy(EnumSet<SharedAccessBlobPermissions> sap,
            int expireTimeInSeconds) {

        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        cal.setTime(new Date());
        cal.add(Calendar.SECOND, expireTimeInSeconds);
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissions(sap);
        policy.setSharedAccessExpiryTime(cal.getTime());
        return policy;

    }

    @SuppressWarnings("unused")
    private static void testAccess(String sasToken, EnumSet<SharedAccessBlobPermissions> permissions,
            SharedAccessBlobHeaders headers, CloudBlobContainer container, CloudBlob blob)
            throws StorageException, URISyntaxException, IOException {
        
        final StorageCredentials credentials;
        final int permissionsErrorCode;
        if (sasToken == null) {
            credentials = StorageCredentialsAnonymous.ANONYMOUS;
            permissionsErrorCode = HttpURLConnection.HTTP_NOT_FOUND;
        } else {
            credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            permissionsErrorCode = HttpURLConnection.HTTP_FORBIDDEN;
        }
        
        if (container != null) {
            container = new CloudBlobContainer(credentials.transformUri(container.getUri()));
            blob = BlobTestHelper.getBlobReference(blob.getProperties().getBlobType(), container, blob.getName());
        }
        else {
            blob = BlobTestHelper.getBlobReference(blob.getProperties().getBlobType(), credentials, blob.getUri());
        }

        if (container != null) {
            if (permissions.contains(SharedAccessBlobPermissions.LIST)) {
                for (ListBlobItem listedBlob : container.listBlobs());
            }
            else {
                try {
                    for (ListBlobItem listedBlob : container.listBlobs());
                    Assert.fail();
                }
                catch (NoSuchElementException ex) {
                    assertEquals(permissionsErrorCode, ((StorageException) ex.getCause()).getHttpStatusCode());
                }
            }
        }

        if (container != null) {
            int uploadSize = (blob.getProperties().getBlobType() == BlobType.BLOCK_BLOB) ? 512 : 0;
            
            if (permissions.contains(SharedAccessBlobPermissions.CREATE) ||
                    permissions.contains(SharedAccessBlobPermissions.WRITE)) {
                BlobTestHelper.uploadNewBlob(container, blob.getProperties().getBlobType(), "", uploadSize, null);
            }
            else {
                try {
                    BlobTestHelper.uploadNewBlob(container, blob.getProperties().getBlobType(), "", uploadSize, null);
                    fail();
                }
                catch (StorageException ex) {
                    assertEquals(permissionsErrorCode, ex.getHttpStatusCode());
                }
            }
        }

        if (container != null && blob instanceof CloudAppendBlob) {
            if (permissions.contains(SharedAccessBlobPermissions.ADD) ||
                    permissions.contains(SharedAccessBlobPermissions.WRITE)) {
                final CloudAppendBlob appBlob = (CloudAppendBlob) blob;
                appBlob.appendBlock(BlobTestHelper.getRandomDataStream(512), 512);
            }
            else {
                try {
                    final CloudAppendBlob appBlob = (CloudAppendBlob) blob;
                    appBlob.appendBlock(BlobTestHelper.getRandomDataStream(512), 512);
                    fail();
                }
                catch (StorageException ex) {
                    assertEquals(permissionsErrorCode, ex.getHttpStatusCode());
                }
            }
        }

        if (permissions.contains(SharedAccessBlobPermissions.READ)) {
            blob.downloadAttributes();

            // Test headers
            if (headers != null) {
                if (headers.getCacheControl() != null) {
                    Assert.assertEquals(headers.getCacheControl(), blob.getProperties().getCacheControl());
                }

                if (headers.getContentDisposition() != null) {
                    Assert.assertEquals(headers.getContentDisposition(), blob.getProperties().getContentDisposition());
                }

                if (headers.getContentEncoding() != null) {
                    Assert.assertEquals(headers.getContentEncoding(), blob.getProperties().getContentEncoding());
                }

                if (headers.getContentLanguage() != null) {
                    Assert.assertEquals(headers.getContentLanguage(), blob.getProperties().getContentLanguage());
                }

                if (headers.getContentType() != null) {
                    Assert.assertEquals(headers.getContentType(), blob.getProperties().getContentType());
                }
            }
        }
        else {
            try {
                blob.downloadAttributes();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(permissionsErrorCode, ex.getHttpStatusCode());
            }
        }

        if (permissions.contains(SharedAccessBlobPermissions.WRITE)) {
            blob.uploadMetadata();
        }
        else {
            try {
                blob.uploadMetadata();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(permissionsErrorCode, ex.getHttpStatusCode());
            }
        }

        if (permissions.contains(SharedAccessBlobPermissions.DELETE)) {
            blob.delete();
        }
        else {
            try {
                blob.delete();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(permissionsErrorCode, ex.getHttpStatusCode());
            }
        }
    }

    private static void testBlobAccess(CloudBlobContainer container, BlobType type,
            EnumSet<SharedAccessBlobPermissions> permissions, SharedAccessBlobHeaders headers) throws StorageException,
            IOException, URISyntaxException, InvalidKeyException {
        CloudBlob blob = BlobTestHelper.uploadNewBlob(container, type, SR.BLOB, 512, null);

        SharedAccessBlobPolicy policy = createSharedAccessPolicy(permissions, 3600);

        String sasToken = blob.generateSharedAccessSignature(policy, headers, null);
        testAccess(sasToken, permissions, headers, null, blob);
    }

    private static void addPermissions(EnumSet<SharedAccessBlobPermissions> permissions, int i) {
        if ((i & 0x1) == 0x1) {
            permissions.add(SharedAccessBlobPermissions.READ);
        }

        if ((i & 0x2) == 0x2) {
            permissions.add(SharedAccessBlobPermissions.ADD);
        }

        if ((i & 0x4) == 0x4) {
            permissions.add(SharedAccessBlobPermissions.CREATE);
        }

        if ((i & 0x8) == 0x8) {
            permissions.add(SharedAccessBlobPermissions.WRITE);
        }

        if ((i & 0x10) == 0x10) {
            permissions.add(SharedAccessBlobPermissions.DELETE);
        }

        if ((i & 0x20) == 0x20) {
            permissions.add(SharedAccessBlobPermissions.LIST);
        }
    }
} public class CloudFileClientTests {
    /**
     * Tests doing a listShares.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListSharesTest() throws StorageException, URISyntaxException {
        CloudFileClient fileClient = FileTestHelper.createCloudFileClient();
        ArrayList<String> shareList = new ArrayList<String>();
        String prefix = UUID.randomUUID().toString();
        try {
            for (int i = 0; i < 30; i++) {
                shareList.add(prefix + i);
                fileClient.getShareReference(prefix + i).create();
            }

            int count = 0;
            for (final CloudFileShare share : fileClient.listShares(prefix)) {
                assertEquals(CloudFileShare.class, share.getClass());
                count++;
            }
            assertEquals(30, count);

            ResultContinuation token = null;
            do {
                ResultSegment<CloudFileShare> segment = fileClient.listSharesSegmented(prefix,
                        EnumSet.allOf(ShareListingDetails.class), 15, token, null, null);

                for (final CloudFileShare share : segment.getResults()) {
                    share.downloadAttributes();
                    assertEquals(CloudFileShare.class, share.getClass());
                    shareList.remove(share.getName());
                    share.delete();
                }

                token = segment.getContinuationToken();
            } while (token != null);

            assertEquals(0, shareList.size());
        }
        finally {
            for (final String shareName : shareList) {
                fileClient.getShareReference(shareName).deleteIfExists();
            }
        }
    }
    
    /**
     * Tests doing a listShares to ensure maxResults validation is working.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListSharesMaxResultsValidationTest() throws StorageException, URISyntaxException {
        CloudFileClient fileClient = FileTestHelper.createCloudFileClient();
        String prefix = UUID.randomUUID().toString();
            
        // Validation should cause each of these to fail
        for (int i = 0; i >= -2; i--) {
            try{ 
                fileClient.listSharesSegmented(
                        prefix, EnumSet.allOf(ShareListingDetails.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(fileClient.listSharesSegmented("thereshouldntbeanyshareswiththisprefix"));
    }

    @Test
    public void testListSharesWithSnapshot() throws StorageException, URISyntaxException {
        CloudFileClient fileClient = FileTestHelper.createCloudFileClient();
        CloudFileShare share = fileClient.getShareReference(UUID.randomUUID().toString());
        try {
            share.create();

            HashMap<String, String> shareMeta = new HashMap<String, String>();
            shareMeta.put("key1", "value1");
            share.setMetadata(shareMeta);
            share.uploadMetadata();

            CloudFileShare snapshot = share.createSnapshot();
            HashMap<String, String> meta2 = new HashMap<String, String>();
            meta2.put("key2", "value2");
            share.setMetadata(meta2);
            share.uploadMetadata();

            CloudFileClient client = FileTestHelper.createCloudFileClient();
            Iterable<CloudFileShare> listResult = client.listShares(share.name, EnumSet.allOf(ShareListingDetails.class), null, null);

            int count = 0;
            boolean originalFound = false;
            boolean snapshotFound = false;
            for (CloudFileShare listShareItem : listResult) {
                if (listShareItem.getName().equals(share.getName()) && !listShareItem.isSnapshot() && !originalFound) {
                    count++;
                    originalFound = true;
                    assertEquals(share.getMetadata(), listShareItem.getMetadata());
                    assertEquals(share.getStorageUri(), listShareItem.getStorageUri());
                } else if (listShareItem.getName().equals(share.getName()) &&
                        listShareItem.isSnapshot() && !snapshotFound) {
                    count++;
                    snapshotFound = true;
                    assertEquals(snapshot.getMetadata(), listShareItem.getMetadata());
                    assertEquals(snapshot.getStorageUri(), listShareItem.getStorageUri());
                }
            }

            assertEquals(2, count);
        }
        finally
        {
            share.deleteIfExists(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null);
        }
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class CloudFileDirectoryTests {

    private CloudFileShare share;

    @Before
    public void cloudFileDirectorySetUp() throws URISyntaxException, StorageException {
        this.share = FileTestHelper.getRandomShareReference();
        this.share.create();
    }

    @After
    public void cloudFileDirectoryTearDown() throws StorageException {
        this.share.deleteIfExists(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null);
    }
    
    /**
     * Test directory name validation.
     */
    @Test
    public void testCloudFileDirectoryNameValidation()
    {
        NameValidator.validateDirectoryName("alpha");
        NameValidator.validateDirectoryName("4lphanum3r1c");
        NameValidator.validateDirectoryName("middle-dash");
        NameValidator.validateDirectoryName("CAPS");
        NameValidator.validateDirectoryName("$root");
        NameValidator.validateDirectoryName("..");
        NameValidator.validateDirectoryName("CLOCK$");
        NameValidator.validateDirectoryName("endslash/");

        invalidDirectoryTestHelper(null, "No null.",
                "Invalid directory name. The name may not be null, empty, or whitespace only.");
        invalidDirectoryTestHelper("middle/slash", "Slashes only at the end.",
                "Invalid directory name. Check MSDN for more information about valid naming.");
        invalidDirectoryTestHelper("illegal\"char", "Illegal character.",
                "Invalid directory name. Check MSDN for more information about valid naming.");
        invalidDirectoryTestHelper("illegal:char?", "Illegal character.",
                "Invalid directory name. Check MSDN for more information about valid naming.");
        invalidDirectoryTestHelper("", "Between 1 and 255 characters.",
                "Invalid directory name. The name may not be null, empty, or whitespace only.");
        invalidDirectoryTestHelper(new String(new char[256]).replace("\0", "n"), "Between 1 and 255 characters.",
                "Invalid directory name length. The name must be between 1 and 255 characters long.");
    }

    private void invalidDirectoryTestHelper(String directoryName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateDirectoryName(directoryName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    private boolean doCloudFileDirectorySetup(CloudFileShare share) throws URISyntaxException, StorageException {
        try {
            CloudFileDirectory rootDirectory = share.getRootDirectoryReference();
            for (int i = 1; i < 3; i++) {
                CloudFileDirectory topDirectory = rootDirectory.getDirectoryReference("TopDir" + i);
                topDirectory.create();

                for (int j = 1; j < 3; j++) {
                    CloudFileDirectory midDirectory = topDirectory.getDirectoryReference("MidDir" + j);
                    midDirectory.create();

                    for (int k = 1; k < 3; k++) {
                        CloudFileDirectory endDirectory = midDirectory.getDirectoryReference("EndDir" + k);
                        endDirectory.create();

                        CloudFile file1 = endDirectory.getFileReference("EndFile" + k);
                        file1.create(0);
                    }
                }

                CloudFile file2 = topDirectory.getFileReference("File" + i);
                file2.create(0);
            }

            return true;
        }
        catch (StorageException e) {
            throw e;
        }
    }

    /**
     * Tests the CloudFileDirectory constructor.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryConstructor() throws URISyntaxException, StorageException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1");
        CloudFileDirectory directory2 = new CloudFileDirectory(directory.getStorageUri(), 
                this.share.getServiceClient().getCredentials());
        assertEquals(directory.getName(), directory2.getName());
        assertEquals(directory.getStorageUri(), directory2.getStorageUri());
        assertEquals(directory.getShare().getStorageUri(), directory2.getShare().getStorageUri());
        assertEquals(FileTestHelper.ensureTrailingSlash(directory.getServiceClient().getStorageUri()),
                FileTestHelper.ensureTrailingSlash(directory2.getServiceClient().getStorageUri()));
    }

    /**
     * Test creation and deletion of a directory.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryCreateAndDelete() throws URISyntaxException, StorageException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1");
        directory.create();
        assertTrue(directory.exists());
        directory.delete();
        assertFalse(directory.exists());
    }

    /**
     * Tests createIfNotExists for a directory.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryCreateIfNotExists() throws StorageException, URISyntaxException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1");
        assertTrue(directory.createIfNotExists());
        assertFalse(directory.createIfNotExists());
        directory.delete();
        assertTrue(directory.createIfNotExists());
    }

    /**
     * Tests deleteIfExists for a directory.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryDeleteIfExists() throws URISyntaxException, StorageException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("directory1");
        assertFalse(directory.deleteIfExists());
        directory.create();
        assertTrue(directory.deleteIfExists());
        assertFalse(directory.deleteIfExists());
    }

    /**
     * Test listFilesAndDirectories for a directory.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryListFilesAndDirectories() throws StorageException, URISyntaxException {
        if (doCloudFileDirectorySetup(this.share)) {
            CloudFileDirectory topDir1 = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
            Iterable<ListFileItem> list1 = topDir1.listFilesAndDirectories();
            assertTrue(list1.iterator().hasNext());
            ArrayList<ListFileItem> simpleList1 = new ArrayList<ListFileItem>();
            for (ListFileItem i : list1) {
                simpleList1.add(i);
            }
            ////Check if for 3 because if there were more than 3, the previous assert would have failed.
            ////So the only thing we need to make sure is that it is not less than 3. 
            assertTrue(simpleList1.size() == 3);

            ListFileItem item11 = simpleList1.get(0);
            assertEquals(item11.getUri().toString(), this.share.getUri() + "/TopDir1/File1");
            assertEquals("File1", ((CloudFile) item11).getName());

            ListFileItem item12 = simpleList1.get(1);
            assertEquals(item12.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir1");
            assertEquals("MidDir1", ((CloudFileDirectory) item12).getName());

            ListFileItem item13 = simpleList1.get(2);
            assertEquals(item13.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir2");
            assertEquals("MidDir2", ((CloudFileDirectory) item13).getName());
            CloudFileDirectory midDir2 = (CloudFileDirectory) item13;

            Iterable<ListFileItem> list2 = midDir2.listFilesAndDirectories();

            ArrayList<ListFileItem> simpleList2 = new ArrayList<ListFileItem>();
            for (ListFileItem i : list2) {
                simpleList2.add(i);
            }
            assertTrue(simpleList2.size() == 2);

            ListFileItem item21 = simpleList2.get(0);
            assertEquals(item21.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir2/EndDir1");
            assertEquals("EndDir1", ((CloudFileDirectory) item21).getName());

            ListFileItem item22 = simpleList2.get(1);
            assertEquals(item22.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir2/EndDir2");
            assertEquals("EndDir2", ((CloudFileDirectory) item22).getName());

            topDir1.downloadAttributes();
            assertNotNull(topDir1.getProperties().getEtag());
            assertNotNull(topDir1.getProperties().getLastModified());

            ResultSegment<ListFileItem> segment1 = topDir1.listFilesAndDirectoriesSegmented();
            assertFalse(segment1.getHasMoreResults());
        }
    }

    /**
     * Test listFilesAndDirectories with prefix
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, CloudTests.class })
    public void testCloudFileDirectoryListFilesAndDirectoriesWithPrefix() throws URISyntaxException, StorageException
    {
        if (doCloudFileDirectorySetup(this.share)) {
            CloudFileDirectory topDir1 = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
            Iterable<ListFileItem> list = topDir1.listFilesAndDirectories("file", null, null);
            ArrayList<ListFileItem> simpleList = new ArrayList<ListFileItem>();
            for (ListFileItem i : list) {
                simpleList.add(i);
            }

            assertTrue(simpleList.size() == 1);
            ListFileItem item = simpleList.get(0);
            assertEquals(this.share.getUri() + "/TopDir1/File1", item.getUri().toString());
            assertEquals("File1", ((CloudFile) item).getName());

            list = topDir1.listFilesAndDirectories("mid", null, null);
            simpleList = new ArrayList<ListFileItem>();
            for (ListFileItem i : list) {
                simpleList.add(i);
            }

            assertTrue(simpleList.size() == 2);
            item = simpleList.get(0);
            ListFileItem item2 = simpleList.get(1);
            assertEquals(this.share.getUri() + "/TopDir1/MidDir1", item.getUri().toString());
            assertEquals("MidDir1", ((CloudFileDirectory) item).getName());
            assertEquals(this.share.getUri() + "/TopDir1/MidDir2", item2.getUri().toString());
            assertEquals("MidDir2", ((CloudFileDirectory) item2).getName());

            ResultSegment<ListFileItem> segmentResults = topDir1.listFilesAndDirectoriesSegmented(
                    "mid",
                    1,
                    null,
                    null,
                    null);

            assertNotNull(segmentResults.getContinuationToken().getNextMarker());
            assertEquals(1, segmentResults.getResults().size());
            item = segmentResults.getResults().get(0);
            assertEquals(this.share.getUri() + "/TopDir1/MidDir1", item.getUri().toString());
            assertEquals("MidDir1", ((CloudFileDirectory)item).getName());

            segmentResults = topDir1.listFilesAndDirectoriesSegmented(
                "mid" /* prefix */,
                null /* maxResults */,
                null /* currentToken */,
                null /* options */,
                null /* operationContext */);

            assertNull(segmentResults.getContinuationToken());
            assertEquals(2, segmentResults.getResults().size());
            item = segmentResults.getResults().get(0);
            assertEquals(this.share.getUri() + "/TopDir1/MidDir1", item.getUri().toString());
            assertEquals("MidDir1", ((CloudFileDirectory)item).getName());
            item = segmentResults.getResults().get(1);
            assertEquals(this.share.getUri() + "/TopDir1/MidDir2", item.getUri().toString());
            assertEquals("MidDir2", ((CloudFileDirectory)item).getName());
        }
    }

    /**
     * Test listFilesAndDirectories for maxResults validation.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryListFilesAndDirectoriesMaxResultsValidation()
            throws StorageException, URISyntaxException {
        if (doCloudFileDirectorySetup(this.share)) {
            CloudFileDirectory topDir =
                    this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
            
            // Validation should cause each of these to fail
            for (int i = 0; i >= -2; i--) {
                try {
                    topDir.listFilesAndDirectoriesSegmented(null, i, null, null, null);
                    fail();
                }
                catch (IllegalArgumentException e) {
                    assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1)
                            .equals(e.getMessage()));
                }
            }
            assertNotNull(topDir.listFilesAndDirectoriesSegmented());
        } else {
            fail();
        }
    }
    
    /**
     * Tests to make sure you can't delete a directory if it is nonempty.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryWithFilesDelete() throws URISyntaxException, StorageException {
        if (doCloudFileDirectorySetup(this.share)) {
            CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference(
                    "TopDir1/MidDir1/EndDir1");
            CloudFile file1 = dir1.getFileReference("EndFile1");

            try {
                dir1.delete();
                fail("Shouldn't be able to delete directory when files still exist in it.");
            }
            catch (StorageException e) {
                assertEquals(e.getHttpStatusCode(), 409);
                assertEquals(e.getMessage(), "The specified directory is not empty.");
            }

            file1.delete();
            dir1.delete();
            assertFalse(file1.exists());
            assertFalse(dir1.exists());
        }
    }
    
    /**
     * Check uploading/downloading directory metadata.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCloudFileDirectoryUploadMetadata() throws StorageException, URISyntaxException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference();
        directory.downloadAttributes();
        Assert.assertEquals(0, directory.getMetadata().size());

        directory.getMetadata().put("key1", "value1");
        directory.uploadMetadata();
        directory.getMetadata().clear();

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

        directory.getMetadata().clear();
        directory.uploadMetadata();
        directory.getMetadata().put("key2", "value2");

        directory.downloadAttributes();
        Assert.assertEquals(0, directory.getMetadata().size());
    }
    
    /**
     * Check if a directory reference with metadata will still have that metadata after being created.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCreateDirectoryWithMetadata() throws StorageException, URISyntaxException {
        String directoryName = "newDirectory1";
        CloudFileDirectory directory = new CloudFileDirectory(
                PathUtility.appendPathToUri(this.share.getStorageUri(), directoryName), directoryName, this.share);
        Assert.assertEquals(0, directory.getMetadata().size());
        
        directory.getMetadata().put("key1", "value1");
        directory.createIfNotExists();
        directory.getMetadata().clear();
        
        directory.downloadAttributes();
        Assert.assertEquals(1, directory.getMetadata().size());
        Assert.assertEquals("value1", directory.getMetadata().get("key1"));
    }
    
    /**
     * Check uploading/downloading invalid directory metadata.
     * @throws URISyntaxException 
     * @throws StorageException 
     */
    @Test
    public void testCloudFileDirectoryInvalidMetadata() throws StorageException, URISyntaxException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference();
        
        // test client-side fails correctly
        testMetadataFailures(directory, null, "value1", true);
        testMetadataFailures(directory, "", "value1", true);
        testMetadataFailures(directory, " ", "value1", true);
        testMetadataFailures(directory, "\n \t", "value1", true);

        testMetadataFailures(directory, "key1", null, false);
        testMetadataFailures(directory, "key1", "", false);
        testMetadataFailures(directory, "key1", " ", false);
        testMetadataFailures(directory, "key1", "\n \t", false);
    }
    
    private static void testMetadataFailures(CloudFileDirectory directory, String key, String value, boolean badKey) throws URISyntaxException {
        directory.getMetadata().put(key, value);
        try {
            directory.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());
            }
        }

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

    @Test
    public void testUnsupportedDirectoryApisWithinShareSnapshot() throws StorageException, URISyntaxException {
        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFileDirectory rootDir = snapshot.getRootDirectoryReference();
        try {
            rootDir.create();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            rootDir.delete();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            rootDir.uploadMetadata();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        
        snapshot.delete();
    }

    @Test
    public void testSupportedDirectoryApisInShareSnapshot() throws StorageException, URISyntaxException {
        CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1");
        dir.deleteIfExists();
        dir.create();
        HashMap<String, String> meta = new HashMap<String, String>();
        meta.put("key1", "value1");
        dir.setMetadata(meta);
        dir.uploadMetadata();
        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFileDirectory snapshotDir = snapshot.getRootDirectoryReference().getDirectoryReference("dir1");
        
        HashMap<String, String> meta2 = new HashMap<String, String>();
        meta2.put("key2", "value2");
        dir.setMetadata(meta2);
        dir.uploadMetadata();
        snapshotDir.downloadAttributes();
        
        assertTrue(snapshotDir.getMetadata().size() == 1 && snapshotDir.getMetadata().get("key1").equals("value1"));
        assertNotNull(snapshotDir.getProperties().getEtag());
        
        dir.downloadAttributes();
        assertTrue(dir.getMetadata().size() == 1 && dir.getMetadata().get("key2").equals("value2"));
        assertNotNull(dir.getProperties().getEtag());
        assertNotEquals(dir.getProperties().getEtag(), snapshotDir.getProperties().getEtag());
        
        final UriQueryBuilder uriBuilder = new UriQueryBuilder();
        uriBuilder.add("sharesnapshot", snapshot.snapshotID);
        uriBuilder.add("restype", "directory");
        CloudFileDirectory snapshotDir2 = new CloudFileDirectory(uriBuilder.addToURI(dir.getUri()), this.share.getServiceClient().getCredentials());
        assertEquals(snapshot.snapshotID, snapshotDir2.getShare().snapshotID);
        assertTrue(snapshotDir2.exists());

        snapshot.delete();
    }

    /*
    [TestMethod]
    [Description("CloudFileDirectory deleting a directory using conditional access")]
    [TestCategory(ComponentCategory.File)]
    [TestCategory(TestTypeCategory.UnitTest)]
    [TestCategory(SmokeTestCategory.NonSmoke)]
    [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)]
    public void CloudFileDirectoryConditionalAccess()
    {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        String name = FileTestHelper.generateRandomShareName();
        CloudFileShare share = client.getShareReference(name);

        try
        {
            share.create();
            if (CloudFileDirectorySetup(share))
            {
                CloudFileDirectory dir1 = share.getRootDirectoryReference().getDirectoryReference("TopDir1/MidDir1/EndDir1");
                CloudFile file1 = dir1.getFileReference("EndFile1");
                file1.delete();
                dir1.FetchAttributes();
                String etag = dir1.Properties.ETag;

                TestHelper.ExpectedException(
                    () => dir1.delete(AccessCondition.GenerateIfNoneMatchCondition(etag), null),
                    "If none match on conditional test should throw",
                    HttpStatusCode.PreconditionFailed,
                    "ConditionNotMet");

                String invalidETag = "\"0x10101010\"";

                TestHelper.ExpectedException(
                    () => dir1.delete(AccessCondition.GenerateIfMatchCondition(invalidETag), null),
                    "If none match on conditional test should throw",
                    HttpStatusCode.PreconditionFailed,
                    "ConditionNotMet");

                dir1.delete(AccessCondition.GenerateIfMatchCondition(etag), null);

                // LastModifiedTime tests
                CloudFileDirectory dir2 = share.getRootDirectoryReference().getDirectoryReference("TopDir1/MidDir1/EndDir2");
                CloudFile file2 = dir2.getFileReference("EndFile2");
                file2.delete();
                dir2.FetchAttributes();
                DateTimeOffset currentModifiedTime = dir2.Properties.LastModified.Value;

                TestHelper.ExpectedException(
                    () => dir2.delete(AccessCondition.GenerateIfModifiedSinceCondition(currentModifiedTime), null),
                    "IfModifiedSince conditional on current modified time should throw",
                    HttpStatusCode.PreconditionFailed,
                    "ConditionNotMet");

                DateTimeOffset pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5));
                TestHelper.ExpectedException(
                    () => dir2.delete(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null),
                    "IfNotModifiedSince conditional on past time should throw",
                    HttpStatusCode.PreconditionFailed,
                    "ConditionNotMet");

                DateTimeOffset ancientTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5));
                TestHelper.ExpectedException(
                    () => dir2.delete(AccessCondition.GenerateIfNotModifiedSinceCondition(ancientTime), null),
                    "IfNotModifiedSince conditional on past time should throw",
                    HttpStatusCode.PreconditionFailed,
                    "ConditionNotMet");

                dir2.delete(AccessCondition.GenerateIfNotModifiedSinceCondition(currentModifiedTime), null);
            }
        }
        finally
        {
            share.delete();
        }
    }
     */

    /**
     * Ensures you cannot create a file without first creating the directory it is located within.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    public void testCloudFileDirectoryFileCreateWithoutDirectory() throws URISyntaxException, StorageException {
        CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("Dir1");
        CloudFile file = dir.getFileReference("file1");

        try {
            file.create(0);
            fail("File shouldn't be created in a directory that wasn't created.");
        }
        catch (StorageException e) {
            assertEquals(e.getErrorCode(), "ParentNotFound");
            assertEquals(e.getHttpStatusCode(), 404);
            assertEquals(e.getMessage(), "The specified parent path does not exist.");
        }

        // File creation directly in the share should pass.
        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file2");
        file2.create(0);

        dir.create();
        file.create(0);
    }

    /**
     * Ensures we cannot create a directory without creating its parent directory first.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryCreateDirectoryWithoutParent() throws URISyntaxException, StorageException {
        CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1");
        CloudFileDirectory dir2 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1/Dir2");
        try {
            dir2.create();
            fail("Directory shouldn't be created when the parent directory wasn't created.");
        }
        catch (StorageException e) {
            assertEquals(e.getErrorCode(), "ParentNotFound");
            assertEquals(e.getHttpStatusCode(), 404);
            assertEquals(e.getMessage(), "The specified parent path does not exist.");
        }

        dir1.create();
        dir2.create();
    }

    /**
     * Tests directory hierarchy navigation.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryGetParent() throws StorageException, URISyntaxException {
        CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("Dir1")
                .getFileReference("File1");
        assertEquals("File1", file.getName());

        // get the file's parent
        CloudFileDirectory parent = file.getParent();
        assertEquals(parent.getName(), "Dir1");

        // get share as parent
        CloudFileDirectory root = parent.getParent();
        assertEquals(root.getName(), "");

        // make sure the parent of the share dir is null
        CloudFileDirectory empty = root.getParent();
        assertNull(empty);

        // from share, get directory reference to share
        root = this.share.getRootDirectoryReference();
        assertEquals("", root.getName());
        assertEquals(this.share.getUri(), root.getUri());

        // make sure the parent of the share dir is null
        empty = root.getParent();
        assertNull(empty);
    }

    /**
     * Tests directory hierarchy navigation.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryGetSubdirectoryAndTraverseBackToParent() throws URISyntaxException, StorageException {
        CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
        CloudFileDirectory subDirectory = directory.getDirectoryReference("MidDir1");
        CloudFileDirectory parent = subDirectory.getParent();
        assertEquals(parent.getName(), directory.getName());
        assertEquals(parent.getUri(), directory.getUri());
    }

    /**
     * Tests directory hierarchy navigation.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryGetParentOnRoot() throws URISyntaxException, StorageException {
        CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
        CloudFileDirectory parent = root.getParent();
        assertNotNull(parent);

        CloudFileDirectory empty = parent.getParent();
        assertNull(empty);
    }

    /**
     * Tests directory hierarchy navigation.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryHierarchicalTraversal() throws URISyntaxException, StorageException {
        // Traverse hierarchically starting with length 1
        CloudFileDirectory directory1 = this.share.getRootDirectoryReference().getDirectoryReference("Dir1");
        CloudFileDirectory subdir1 = directory1.getDirectoryReference("Dir2");
        CloudFileDirectory parent1 = subdir1.getParent();
        assertEquals(parent1.getName(), directory1.getName());

        CloudFileDirectory subdir2 = subdir1.getDirectoryReference("Dir3");
        CloudFileDirectory parent2 = subdir2.getParent();
        assertEquals(parent2.getName(), subdir1.getName());

        CloudFileDirectory subdir3 = subdir2.getDirectoryReference("Dir4");
        CloudFileDirectory parent3 = subdir3.getParent();
        assertEquals(parent3.getName(), subdir2.getName());

        CloudFileDirectory subdir4 = subdir3.getDirectoryReference("Dir5");
        CloudFileDirectory parent4 = subdir4.getParent();
        assertEquals(parent4.getName(), subdir3.getName());
    }

    /**
     * Tests directory hierarchy navigation.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    public void testCloudFileDirectoryFileParentValidate() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1")
                .getDirectoryReference("MidDir1").getDirectoryReference("EndDir1").getFileReference("EndFile1");
        CloudFileDirectory directory = file.getParent();
        assertEquals(directory.getName(), "EndDir1");
        assertEquals(directory.getUri().toString(), this.share.getUri() + "/TopDir1/MidDir1/EndDir1");
    }

    /**
     * Tests directory creation with empty names.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryGetEmptySubDirectory() throws URISyntaxException, StorageException {
        CloudFileDirectory root = this.share.getRootDirectoryReference().getDirectoryReference("TopDir1");
        try {
            root.getDirectoryReference("");
            fail("Subdirectory references shouldn't work with empty strings.");
        }
        catch (IllegalArgumentException e) {
            assertEquals(e.getMessage(), "The argument must not be null or an empty string. Argument name: itemName.");
        }
    }

    /**
     * Tests directory creation with absolute uris.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testCloudFileDirectoryAbsoluteUriAppended() throws URISyntaxException, StorageException {
        CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference(
                this.share.getUri().toString());
        assertEquals(PathUtility.appendPathToUri(this.share.getStorageUri(), this.share.getUri().toString())
                .getPrimaryUri(), dir.getUri());
        assertEquals(new URI(this.share.getUri() + "/" + this.share.getUri()), dir.getUri());
        dir = this.share.getRootDirectoryReference().getDirectoryReference(this.share.getUri() + "/TopDir1");
        assertEquals(
                PathUtility.appendPathToUri(this.share.getStorageUri(), this.share.getUri().toString() + "/TopDir1")
                        .getPrimaryUri(), dir.getUri());
    }

    /**
     * Test specific deleteIfExists case.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudFileDirectoryDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException {
        final CloudFileDirectory directory = this.share.getRootDirectoryReference().getDirectoryReference(
                "directory");

        try {
            directory.delete();
            fail("Directory should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.RESOURCE_NOT_FOUND, e.getErrorCode());
        }

        assertFalse(directory.exists());
        assertFalse(directory.deleteIfExists());

        directory.create();
        assertTrue(directory.exists());

        assertTrue(directory.deleteIfExists());
        assertFalse(directory.deleteIfExists());

        // check if second condition works in delete if exists
        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

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

        directory.create();
        assertFalse(directory.deleteIfExists(null, null, ctx));
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class CloudFileShareTests {

    protected static CloudFileClient client;
    protected CloudFileShare share;

    @Before
    public void fileShareTestMethodSetUp() throws StorageException, URISyntaxException {
        this.share = FileTestHelper.getRandomShareReference();
    }

    @After
    public void fileShareTestMethodTearDown() throws StorageException {
        this.share.deleteIfExists(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null);
    }
    
    /**
     * Test share name validation.
     */
    @Test
    public void testCloudShareNameValidation()
    {
        NameValidator.validateShareName("alpha");
        NameValidator.validateShareName("4lphanum3r1c");
        NameValidator.validateShareName("middle-dash");

        invalidShareTestHelper(null, "Null not allowed.", "Invalid share name. The name may not be null, empty, or whitespace only.");
        invalidShareTestHelper("$root", "Alphanumeric or dashes only.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("double--dash", "No double dash.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("CapsLock", "Lowercase only.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("illegal$char", "Alphanumeric or dashes only.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("illegal!char", "Alphanumeric or dashes only.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("white space", "Alphanumeric or dashes only.", "Invalid share name. Check MSDN for more information about valid naming.");
        invalidShareTestHelper("2c", "Between 3 and 63 characters.", "Invalid share name length. The name must be between 3 and 63 characters long.");
        invalidShareTestHelper(new String(new char[64]).replace("\0", "n"), "Between 3 and 63 characters.", "Invalid share name length. The name must be between 3 and 63 characters long.");
    }

    private void invalidShareTestHelper(String shareName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateShareName(shareName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    /**
     * Validate share references
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCloudFileShareReference() throws StorageException, URISyntaxException {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare share = client.getShareReference("share");

        CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory3");
        CloudFileDirectory directory2 = directory.getDirectoryReference("directory4");

        assertEquals(share.getStorageUri().toString(), directory.getShare().getStorageUri().toString());
        assertEquals(share.getStorageUri().toString(), directory2.getShare().getStorageUri().toString());
        assertEquals(share.getStorageUri().toString(), directory2.getParent().getShare().getStorageUri().toString());
    }

    /**
     * Try to create a share after it is created
     * 
     * @throws StorageException
     */
    @Test
    public void testCloudFileShareCreate() throws StorageException {
        this.share.create();
        assertTrue(this.share.exists());
        try {
            this.share.create();
            fail("Share already existed but was created anyway.");
        }
        catch (StorageException e) {
            assertEquals(e.getErrorCode(), "ShareAlreadyExists");
            assertEquals(e.getHttpStatusCode(), 409);
            assertEquals(e.getMessage(), "The specified share already exists.");
        }
    }

    /**
     * CreateIfNotExists test.
     * 
     * @throws StorageException
     */
    @Test
    public void testCloudFileShareCreateIfNotExists() throws StorageException {
        assertTrue(this.share.createIfNotExists());
        assertTrue(this.share.exists());
        assertFalse(this.share.createIfNotExists());
    }

    /**
     * DeleteIfExists test.
     * 
     * @throws StorageException
     */
    @Test
    public void testCloudFileShareDeleteIfExists() throws StorageException {
        assertFalse(this.share.deleteIfExists());
        this.share.create();
        assertTrue(this.share.deleteIfExists());
        assertFalse(this.share.exists());
        assertFalse(this.share.deleteIfExists());
    }

    /**
     * Check a share's existence
     * 
     * @throws StorageException
     */
    @Test
    public void testCloudFileShareExists() throws StorageException {
        assertFalse(this.share.exists());

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

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

    /**
     * Set and delete share permissions
     * 
     * @throws URISyntaxException
     * @throws StorageException
     * @throws InterruptedException
     */
    @Test
    @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class })
    public void testCloudFileShareSetPermissions()
            throws StorageException, InterruptedException, URISyntaxException {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        this.share.create();

        FileSharePermissions permissions = this.share.downloadPermissions();
        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();

        SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
        policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.CREATE));
        policy.setSharedAccessStartTime(start);
        policy.setSharedAccessExpiryTime(expiry);
        permissions.getSharedAccessPolicies().put("key1", policy);

        // Set permissions and wait for them to propagate
        this.share.uploadPermissions(permissions);
        Thread.sleep(30000);
        
        // Check if permissions were set
        CloudFileShare share2 = client.getShareReference(this.share.getName());
        assertPermissionsEqual(permissions, share2.downloadPermissions());

        // Clear permissions and wait for them to propagate
        permissions.getSharedAccessPolicies().clear();
        this.share.uploadPermissions(permissions);
        Thread.sleep(30000);

        // Check if permissions were cleared
        assertPermissionsEqual(permissions, share2.downloadPermissions());
    }

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

        policy.setPermissionsFromString("rcwdl");
        assertEquals(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.CREATE,
                SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.DELETE, SharedAccessFilePermissions.LIST),
                policy.getPermissions());

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

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

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

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

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

        policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.CREATE,
                SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.DELETE, SharedAccessFilePermissions.LIST));
        assertEquals("rcwdl", policy.permissionsToString());

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

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

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

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

    /**
     * Check uploading/downloading share metadata.
     * 
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCloudFileShareUploadMetadata() throws StorageException, URISyntaxException {
        this.share.getMetadata().put("key1", "value1");
        this.share.create();
        assertEquals(1, this.share.getMetadata().size());
        assertEquals("value1", this.share.getMetadata().get("key1"));

        CloudFileShare share2 = this.share.getServiceClient().getShareReference(this.share.getName());
        share2.downloadAttributes();
        assertEquals(1, share2.getMetadata().size());
        assertEquals("value1", share2.getMetadata().get("key1"));

        this.share.getMetadata().put("key2", "value2");

        assertEquals(2, this.share.getMetadata().size());
        assertEquals("value1", this.share.getMetadata().get("key1"));
        assertEquals("value2", this.share.getMetadata().get("key2"));
        this.share.uploadMetadata();

        assertEquals(2, this.share.getMetadata().size());
        assertEquals("value1", this.share.getMetadata().get("key1"));
        assertEquals("value2", this.share.getMetadata().get("key2"));

        share2.downloadAttributes();
        assertEquals(2, this.share.getMetadata().size());
        assertEquals("value1", this.share.getMetadata().get("key1"));
        assertEquals("value2", this.share.getMetadata().get("key2"));

        Iterable<CloudFileShare> shares = this.share.getServiceClient().listShares(this.share.getName(),
                EnumSet.of(ShareListingDetails.METADATA), null, null);

        for (CloudFileShare share3 : shares) {
            assertEquals(2, share3.getMetadata().size());
            assertEquals("value1", share3.getMetadata().get("key1"));
            assertEquals("value2", this.share.getMetadata().get("key2"));
        }

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

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

    /**
     * Check uploading/downloading invalid share metadata.
     */
    @Test
    public void testCloudFileShareInvalidMetadata() {
        // test client-side fails correctly
        testMetadataFailures(this.share, null, "value1", true);
        testMetadataFailures(this.share, "", "value1", true);
        testMetadataFailures(this.share, " ", "value1", true);
        testMetadataFailures(this.share, "\n \t", "value1", true);

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

    private static void testMetadataFailures(CloudFileShare share, String key, String value, boolean badKey) {
        share.getMetadata().put(key, value);
        try {
            share.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());
            }
        }

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

    /**
     * Tests whether Share Stats can be updated and downloaded.
     * 
     * @throws StorageException
     * @throws IOException
     * @throws URISyntaxException
     */
    @Test
    @Category({ CloudTests.class })
    public void testGetShareStats() throws StorageException, IOException, URISyntaxException {
        share.createIfNotExists();
        ShareStats stats = share.getStats();
        assertNotNull(stats);
        assertEquals(0, stats.getUsage());

        FileTestHelper.uploadNewFile(share, 512, null);

        stats = share.getStats();
        assertNotNull(stats);
        assertEquals(1, stats.getUsage());
    }

    /**
     * Test that Share Quota can be set, but only to allowable values.
     * 
     * @throws StorageException 
     * @throws URISyntaxException 
     */
    @Test
    public void testCloudFileShareQuota() throws StorageException, URISyntaxException {
        // Share quota defaults to 5120
        this.share.createIfNotExists();
        this.share.downloadAttributes();
        assertNotNull(this.share.getProperties().getShareQuota());
        int shareQuota = FileConstants.MAX_SHARE_QUOTA;
        assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue());

        // Upload new share quota
        shareQuota = 8;
        this.share.getProperties().setShareQuota(shareQuota);
        this.share.uploadProperties();
        this.share.downloadAttributes();
        assertNotNull(this.share.getProperties().getShareQuota());
        assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue());
        this.share.delete();

        // Create a share with quota already set
        shareQuota = 16;
        this.share = FileTestHelper.getRandomShareReference();
        this.share.getProperties().setShareQuota(shareQuota);
        this.share.create();
        assertNotNull(this.share.getProperties().getShareQuota());
        assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue());
        this.share.downloadAttributes();
        assertNotNull(this.share.getProperties().getShareQuota());
        assertEquals(shareQuota, this.share.getProperties().getShareQuota().intValue());

        // Attempt to set illegal share quota
        try {
            shareQuota = FileConstants.MAX_SHARE_QUOTA + 1;
            this.share.getProperties().setShareQuota(shareQuota);
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals(String.format(SR.PARAMETER_NOT_IN_RANGE, "Share Quota", 1, FileConstants.MAX_SHARE_QUOTA),
                    e.getMessage());
        }
    }

    /**
     * Test that Share Quota can be set, but only to allowable values.
     * 
     * @throws StorageException 
     * @throws URISyntaxException 
     */
    @Test
    public void testCloudFileShareQuotaListing() throws StorageException, URISyntaxException {
        int shareQuota = 16;
        this.share.getProperties().setShareQuota(shareQuota);
        this.share.createIfNotExists();

        Iterable<CloudFileShare> shares = this.share.getServiceClient().listShares(this.share.getName());

        for (CloudFileShare fileShare : shares) {
            assertEquals(shareQuota, fileShare.getProperties().getShareQuota().intValue());
        }
    }

    /**
     * Test specific deleteIfExists case.
     * 
     * @throws StorageException
     */
    @Test
    public void testCloudFileShareDeleteIfExistsErrorCode() throws StorageException {
        try {
            this.share.delete();
            fail("Share should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.SHARE_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 {
                        CloudFileShareTests.this.share.delete();
                        assertFalse(CloudFileShareTests.this.share.exists());
                    }
                    catch (StorageException e) {
                        fail("Delete should succeed.");
                    }
                }
            }
        });

        this.share.create();

        // Share deletes succeed before garbage collection occurs.
        assertTrue(this.share.deleteIfExists(null, null, ctx));
    }

    @Test
    public void testCreateShareSnapshot() throws StorageException, URISyntaxException, IOException {
        // create share with metadata
        this.share.create();
        assertTrue(this.share.exists());
        HashMap<String, String> shareMeta = new HashMap<String, String>();
        shareMeta.put("key1", "value1");
        this.share.setMetadata(shareMeta);
        this.share.uploadMetadata();

        CloudFileDirectory dir1 = this.share.getRootDirectoryReference().getDirectoryReference("dir1");
        dir1.create();
        CloudFile file1 = dir1.getFileReference("file1");
        file1.create(1024);
        ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(1024);
        file1.upload(srcStream, 1024);

        // create directory with metadata
        HashMap<String, String> dirMeta = new HashMap<String, String>();
        dirMeta.put("key2", "value2");
        dir1.setMetadata(dirMeta);
        dir1.uploadMetadata();

        // verify that exists() call on snapshot populates metadata
        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID);
        assertTrue(snapshotRef.exists());
        assertTrue(snapshotRef.getMetadata().size() == 1 && snapshotRef.getMetadata().get("key1").equals("value1"));

        // verify that downloadAttributes() populates metadata
        CloudFileShare snapshotRef2 = client.getShareReference(snapshot.name, snapshot.snapshotID);
        snapshotRef2.downloadAttributes();
        snapshot.downloadAttributes();
        assertTrue(snapshotRef2.getMetadata().size() == 1 && snapshotRef2.getMetadata().get("key1").equals("value1"));
        assertTrue(snapshot.getMetadata().size() == 1 && snapshot.getMetadata().get("key1").equals("value1"));

        // verify that exists() populates the metadata
        CloudFileDirectory snapshotDir1 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1");
        snapshotDir1.exists();
        assertTrue(snapshotDir1.getMetadata().size() == 1 && snapshotDir1.getMetadata().get("key2").equals("value2"));

        // verify that downloadAttributes() populates the metadata
        CloudFileDirectory snapshotDir2 = snapshot.getRootDirectoryReference().getDirectoryReference("dir1");
        snapshotDir2.downloadAttributes();
        assertTrue(snapshotDir2.getMetadata().size() == 1 && snapshotDir2.getMetadata().get("key2").equals("value2"));

        // create snapshot with metadata
        HashMap<String, String> shareMeta2 = new HashMap<String, String>();
        shareMeta2.put("abc", "def");
        CloudFileShare snapshotRef3 = this.share.createSnapshot(shareMeta2, null, null, null);
        CloudFileShare snapshotRef4 = client.getShareReference(snapshotRef3.name, snapshotRef3.snapshotID);
        assertTrue(snapshotRef4.exists());
        assertTrue(snapshotRef4.getMetadata().size() == 1 && snapshotRef4.getMetadata().get("abc").equals("def"));

        final UriQueryBuilder uriBuilder = new UriQueryBuilder();
        uriBuilder.add("sharesnapshot", snapshot.snapshotID);
        CloudFileShare snapshotRef5 = new CloudFileShare(uriBuilder.addToURI(this.share.getUri()),
                                                         this.share.getServiceClient().getCredentials());
        assertEquals(snapshot.snapshotID, snapshotRef5.snapshotID);
        assertTrue(snapshotRef5.exists());

        snapshot.delete();
    }
    
    @Test
    public void testDeleteShareSnapshotOptions() throws StorageException, URISyntaxException, IOException {
        // create share with metadata
        this.share.create();
        assertTrue(this.share.exists());

        // verify that exists() call on snapshot populates metadata
        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID);
        assertTrue(snapshotRef.exists());

        try {
            share.delete();
        }
        catch (final StorageException e) {
            assertEquals(StorageErrorCodeStrings.SHARE_HAS_SNAPSHOTS, e.getErrorCode());
        }
        
        share.delete(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null);
        assertFalse(share.exists());
        assertFalse(snapshot.exists());
    }

    @Test
    public void testListFilesAndDirectoriesWithinShareSnapshot() throws StorageException, URISyntaxException {
        this.share.create();

        CloudFileDirectory myDir = this.share.getRootDirectoryReference().getDirectoryReference("mydir");
        myDir.create();
        myDir.getFileReference("myfile").create(1024);
        myDir.getDirectoryReference("yourDir").create();
        assertTrue(this.share.exists());

        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare snapshotRef = client.getShareReference(snapshot.name, snapshot.snapshotID);

        Iterable<ListFileItem> listResult = snapshotRef.getRootDirectoryReference().listFilesAndDirectories();
        int count = 0;
        for (ListFileItem listFileItem : listResult) {
            count++;
            assertEquals("mydir", ((CloudFileDirectory) listFileItem).getName());
        }

        assertEquals(1, count);

        count = 0;
        listResult = snapshotRef.getRootDirectoryReference().getDirectoryReference("mydir").listFilesAndDirectories();
        for (ListFileItem listFileItem : listResult) {
            if (listFileItem instanceof CloudFileDirectory) {
                count++;
                assertEquals("yourDir", ((CloudFileDirectory) listFileItem).getName());
            }
            else {
                count++;
                assertEquals("myfile", ((CloudFile) listFileItem).getName());
            }
        }

        assertEquals(2, count);

        snapshot.delete();
    }

    @Test
    public void testUnsupportedApisShareSnapshot() throws StorageException, URISyntaxException {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        this.share.create();
        this.share.downloadPermissions();
        CloudFileShare snapshot = this.share.createSnapshot();
        try {
            snapshot.createSnapshot();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            snapshot.downloadPermissions();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            snapshot.getStats();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            snapshot.uploadMetadata();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            FileSharePermissions permissions = new FileSharePermissions();
            snapshot.uploadPermissions(permissions);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            snapshot.uploadProperties();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }

        snapshot.delete();
    }

    private static void assertPermissionsEqual(FileSharePermissions expected, FileSharePermissions actual) {
        HashMap<String, SharedAccessFilePolicy> expectedPolicies = expected.getSharedAccessPolicies();
        HashMap<String, SharedAccessFilePolicy> actualPolicies = actual.getSharedAccessPolicies();
        assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size());
        for (String name : expectedPolicies.keySet()) {
            assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name));
            SharedAccessFilePolicy expectedPolicy = expectedPolicies.get(name);
            SharedAccessFilePolicy 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());
        }
    }
} @Category({ CloudTests.class })
public class CloudFileTests {
    protected CloudFileShare share;

    @Before
    public void fileTestMethodSetUp() throws URISyntaxException, StorageException {
        this.share = FileTestHelper.getRandomShareReference();
        this.share.create();
    }

    @After
    public void fileTestMethodTearDown() throws StorageException {
        this.share.deleteIfExists(DeleteShareSnapshotsOption.INCLUDE_SNAPSHOTS, null, null, null);
    }

    /**
     * Test file name validation.
     */
    @Test
    public void CloudFileNameValidation()
    {
        NameValidator.validateFileName("alpha");
        NameValidator.validateFileName("4lphanum3r1c");
        NameValidator.validateFileName("middle-dash");
        NameValidator.validateFileName("CAPS");
        NameValidator.validateFileName("$root");

        invalidFileTestHelper(null, "No null.", "Invalid file name. The name may not be null, empty, or whitespace only.");
        invalidFileTestHelper("..", "Reserved.", "Invalid file name. This name is reserved.");
        invalidFileTestHelper("Clock$", "Reserved.", "Invalid file name. This name is reserved.");
        invalidFileTestHelper("endslash/", "No slashes.", "Invalid file name. Check MSDN for more information about valid naming.");
        invalidFileTestHelper("middle/slash", "No slashes.", "Invalid file name. Check MSDN for more information about valid naming.");
        invalidFileTestHelper("illegal\"char", "Illegal characters.", "Invalid file name. Check MSDN for more information about valid naming.");
        invalidFileTestHelper("illegal:char?", "Illegal characters.", "Invalid file name. Check MSDN for more information about valid naming.");
        invalidFileTestHelper("", "Between 1 and 255 characters.", "Invalid file name. The name may not be null, empty, or whitespace only.");
        invalidFileTestHelper(new String(new char[256]).replace("\0", "n"), "Between 1 and 255 characters.", "Invalid file name length. The name must be between 1 and 255 characters long.");
    }
    
    private void invalidFileTestHelper(String fileName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateFileName(fileName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }
    
    /**
     * Test file creation and deletion.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileCreateAndDelete() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        file.create(0);
        assertTrue(file.exists());
        file.delete();
    }

    @Test
    public void testCloudFileCreate() throws StorageException, URISyntaxException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");

        assertFalse(file.exists());

        // Create
        file.create(512);
        assertTrue(file.exists());

        // Create again (should succeed)
        file.create(128);
        assertTrue(file.exists());
    }

    /**
     * Test file constructor.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileConstructor() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        CloudFile file2 = new CloudFile(file.getStorageUri(), file.getServiceClient().getCredentials());
        assertEquals(file.getName(), file2.getName());
        assertEquals(file.getStorageUri(), file2.getStorageUri());
        assertEquals(file.getShare().getStorageUri(), file2.getShare().getStorageUri());
        assertEquals(FileTestHelper.ensureTrailingSlash(file.getServiceClient().getStorageUri()),
                FileTestHelper.ensureTrailingSlash(file2.getServiceClient().getStorageUri()));

        CloudFile file3 = new CloudFile(file2);
        assertEquals(file3.getName(), file2.getName());
        assertEquals(file3.getStorageUri(), file2.getStorageUri());
        assertEquals(file3.getShare().getStorageUri(), file2.getShare().getStorageUri());
        assertEquals(FileTestHelper.ensureTrailingSlash(file3.getServiceClient().getStorageUri()),
                FileTestHelper.ensureTrailingSlash(file2.getServiceClient().getStorageUri()));
    }

    /**
     * Test file resizing.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileResize() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");

        file.create(1024);
        assertEquals(1024, file.getProperties().getLength());
        file2.downloadAttributes();
        assertEquals(1024, file2.getProperties().getLength());
        file2.getProperties().setContentType("text/plain");
        file2.uploadProperties();
        file.resize(2048);
        assertEquals(2048, file.getProperties().getLength());
        file.downloadAttributes();
        assertEquals("text/plain", file.getProperties().getContentType());
        file2.downloadAttributes();
        assertEquals(2048, file2.getProperties().getLength());

        // Resize to 0 length
        file.resize(0);
        assertEquals(0, file.getProperties().getLength());
        file.downloadAttributes();
        assertEquals("text/plain", file.getProperties().getContentType());
        file2.downloadAttributes();
        assertEquals(0, file2.getProperties().getLength());
    }

    /**
     * Test file creation with invalid sizes.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    public void testCloudFileCreateInvalidSize() throws StorageException, URISyntaxException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");

        try {
            file.create(-1);
            fail("Creating a file with size<0 should fail");
        }
        catch (StorageException e) {
            assertEquals(e.getHttpStatusCode(), 400);
            assertEquals(e.getMessage(), "The value for one of the HTTP headers is not in the correct format.");
        }

        try {
            file.create(1L * 1024 * 1024 * 1024 * 1024 + 1);
            fail("Creating a file with size>1TB should fail");
        }
        catch (StorageException e) {
            assertEquals(e.getHttpStatusCode(), 400);
            assertEquals(e.getMessage(), "The value for one of the HTTP headers is not in the correct format.");
        }
    }

    /**
     * Test file deleteIfExists.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileDeleteIfExists() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        assertFalse(file.deleteIfExists());
        file.create(0);
        assertTrue(file.deleteIfExists());
        assertFalse(file.deleteIfExists());

    }

    /**
     * Test file exits method.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileExists() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");

        assertFalse(file2.exists());

        file.create(2048);

        assertTrue(file2.exists());
        assertEquals(2048, file2.getProperties().getLength());

        file.delete();

        assertFalse(file2.exists());
    }

    /**
     * Test file getProperties.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileDownloadAttributes() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file29");
        file.create(1024);
        assertEquals(1024, file.getProperties().getLength());
        assertNotNull(file.getProperties().getEtag());
        GregorianCalendar now = new GregorianCalendar();
        now.add(GregorianCalendar.MINUTE, 5);
        assertTrue(file.getProperties().getLastModified().before(now.getTime()));
        assertNull(file.getProperties().getCacheControl());
        assertNull(file.getProperties().getContentDisposition());
        assertNull(file.getProperties().getContentEncoding());
        assertNull(file.getProperties().getContentLanguage());
        assertNull(file.getProperties().getContentType());
        assertNull(file.getProperties().getContentMD5());

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file29");
        file2.downloadAttributes();
        assertEquals(1024, file2.getProperties().getLength());
        assertEquals(file.getProperties().getEtag(), file2.getProperties().getEtag());
        assertEquals(file.getProperties().getLastModified(), file2.getProperties().getLastModified());
        assertNull(file2.getProperties().getCacheControl());
        assertNull(file2.getProperties().getContentDisposition());
        assertNull(file2.getProperties().getContentEncoding());
        assertNull(file2.getProperties().getContentLanguage());
        assertEquals("application/octet-stream", file2.getProperties().getContentType());
        assertNull(file2.getProperties().getContentMD5());
    }

    /**
     * Test file setProperties.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileSetProperties() throws StorageException, URISyntaxException, InterruptedException {

        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        file.create(1024);
        String eTag = file.getProperties().getEtag();
        Date lastModified = file.getProperties().getLastModified();

        Thread.sleep(1000);

        file.getProperties().setCacheControl("no-transform");
        file.getProperties().setContentDisposition("attachment");
        file.getProperties().setContentEncoding("gzip");
        file.getProperties().setContentLanguage("tr,en");
        file.getProperties().setContentMD5("MDAwMDAwMDA=");
        file.getProperties().setContentType("text/html");
        file.uploadProperties();
        assertTrue(file.getProperties().getLastModified().after(lastModified));
        assertTrue(!eTag.equals(file.getProperties().getEtag()));

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");
        file2.downloadAttributes();
        assertEquals("no-transform", file2.getProperties().getCacheControl());
        assertEquals("attachment", file2.getProperties().getContentDisposition());
        assertEquals("gzip", file2.getProperties().getContentEncoding());
        assertEquals("tr,en", file2.getProperties().getContentLanguage());
        assertEquals("MDAwMDAwMDA=", file2.getProperties().getContentMD5());
        assertEquals("text/html", file2.getProperties().getContentType());

        CloudFile file3 = this.share.getRootDirectoryReference().getFileReference("file1");
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        FileRequestOptions options = new FileRequestOptions();
        options.setDisableContentMD5Validation(true);
        file3.download(stream, null, options, null);
        FileTestHelper.assertAreEqual(file2.getProperties(), file3.getProperties());

        CloudFileDirectory rootDirectory = this.share.getRootDirectoryReference();
        Iterator<ListFileItem> iter = rootDirectory.listFilesAndDirectories().iterator();
        CloudFile file4 = null;
        if (iter.hasNext()) {
            file4 = (CloudFile) iter.next();
        }
        else {
            fail("Expecting a file here.");
        }
        assertEquals(file2.getProperties().getLength(), file4.getProperties().getLength());
    }

    /**
     * Test file creation with metadata.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileCreateWithMetadata() throws URISyntaxException, StorageException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        HashMap<String, String> meta = new HashMap<String, String>();
        meta.put("key1", "value1");
        file.setMetadata(meta);
        file.create(1024);

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");
        file2.downloadAttributes();
        assertEquals(1, file2.getMetadata().size());
        assertEquals("value1", file2.getMetadata().get("key1"));
        file2.setMetadata(new HashMap<String, String>());
        file2.uploadMetadata();

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

    @Test
    @Category(SlowTests.class)
    public void testCopyBlockBlobSas() throws Exception {
        // Create source on server.
        final CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();
        try {
            container.create();
            final CloudBlockBlob source = container.getBlockBlobReference("source");

            source.getMetadata().put("Test", "value");
            final String data = "String data";
            source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

            final CloudFile destination = doCloudBlobCopy(source, data.length());

            final String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null);
            assertEquals(data, copyData);
        }
        finally {
            container.deleteIfExists();
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyPageBlobSas() throws Exception {
        // Create source on server.
        final CloudBlobContainer container = BlobTestHelper.getRandomContainerReference();
        try {
            container.create();
            final CloudPageBlob source = container.getPageBlobReference("source");

            source.getMetadata().put("Test", "value");
            final int length = 512;
            final ByteArrayInputStream data = BlobTestHelper.getRandomDataStream(length);
            source.upload(data, length);

            final CloudFile destination = doCloudBlobCopy(source, length);

            final ByteArrayOutputStream copyData = new ByteArrayOutputStream();
            destination.download(copyData);
            BlobTestHelper.assertStreamsAreEqual(data, new ByteArrayInputStream(copyData.toByteArray()));
        }
        finally {
            container.deleteIfExists();
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyFileSasToSas() throws InvalidKeyException, URISyntaxException, StorageException,
            IOException, InterruptedException {
        this.doCloudFileCopy(true, true);
    }

    // There is no testCopyFileToSas() because there is no way for the SAS destination to access a shared key source.
    // This means it would require the source to have public access, which files do not support.
    @Test
    @Category(SlowTests.class)
    public void testCopyFileSas() throws InvalidKeyException, URISyntaxException, StorageException,
            IOException, InterruptedException {
        this.doCloudFileCopy(true, false);
    }

    @Test
    @Category(SlowTests.class)
    public void testCopyFile() throws InvalidKeyException, URISyntaxException, StorageException, IOException,
            InterruptedException {
        // This works because we use Shared Key for source and destination.
        // The request is then signed with the key so the service knows we are authorized to access both.
        this.doCloudFileCopy(false, false);
    }

    @Test
    public void testCopyWithChineseChars() throws StorageException, IOException, URISyntaxException
    {
        String data = "sample data chinese chars 阿䶵";
        CloudFileDirectory rootDirectory = this.share.getRootDirectoryReference();
        CloudFile copySource = rootDirectory.getFileReference("sourcechinescharsblob阿䶵.txt");
        copySource.uploadText(data);

        assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob阿䶵.txt", copySource.getUri().toString());
        assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt", copySource
                .getUri().toASCIIString());

        CloudFile copyDestination = rootDirectory.getFileReference("destchinesecharsblob阿䶵.txt");

        assertEquals(rootDirectory.getUri() + "/destchinesecharsblob阿䶵.txt", copyDestination.getUri().toString());
        assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt",
                copyDestination.getUri().toASCIIString());

        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {
            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject();
                
                try {
                    CloudFileDirectory rootDirectory = CloudFileTests.this.share.getRootDirectoryReference();
                    
                    // Test the copy destination request url
                    assertEquals(rootDirectory.getUri() + "/destchinesecharsblob%E9%98%BF%E4%B6%B5.txt",
                            con.getURL().toString());
                    
                    // Test the copy source request property
                    assertEquals(rootDirectory.getUri() + "/sourcechinescharsblob%E9%98%BF%E4%B6%B5.txt",
                            con.getRequestProperty("x-ms-copy-source"));
                } catch (Exception e) {
                    fail("This code should not generate any exceptions.");
                } 
            }
        });
        
        copyDestination.startCopy(copySource.getUri(), null, null, null, ctx);
        copyDestination.startCopy(copySource, null, null, null, ctx);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testCopyFileWithMetadataOverride() throws URISyntaxException, StorageException, IOException,
            InterruptedException {
        Calendar calendar = Calendar.getInstance(Utility.UTC_ZONE);
        String data = "String data";
        CloudFile source = this.share.getRootDirectoryReference().getFileReference("source");
        FileTestHelper.setFileProperties(source);
        
        // do this to make sure the set MD5 can be compared, otherwise when the dummy value
        // doesn't match the actual MD5 an exception would be thrown
        FileRequestOptions options = new FileRequestOptions();
        options.setDisableContentMD5Validation(true);

        source.getMetadata().put("Test", "value");
        source.uploadText(data);

        CloudFile copy = this.share.getRootDirectoryReference().getFileReference("copy");
        copy.getMetadata().put("Test2", "value2");
        String copyId = copy.startCopy(FileTestHelper.defiddler(source));
        FileTestHelper.waitForCopy(copy);

        assertEquals(CopyStatus.SUCCESS, copy.getCopyState().getStatus());
        assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(),
                copy.getCopyState().getSource().getPath());
        assertEquals(data.length(), copy.getCopyState().getTotalBytes().intValue());
        assertEquals(data.length(), copy.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, copy.getCopyState().getCopyId());
        assertTrue(copy.getCopyState().getCompletionTime().compareTo(new Date(calendar.get(Calendar.MINUTE) - 1)) > 0);

        String copyData = copy.downloadText(Constants.UTF8_CHARSET, null, options, null);
        assertEquals(data, copyData);

        copy.downloadAttributes();
        source.downloadAttributes();
        FileProperties prop1 = copy.getProperties();
        FileProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value2", copy.getMetadata().get("Test2"));
        assertFalse(copy.getMetadata().containsKey("Test"));

        copy.delete();
    }


    /**
     * Start copying a file and then abort
     * 
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCopyFileAbort() throws StorageException, URISyntaxException, IOException {
        final int length = 128;
        CloudFile originalFile = FileTestHelper.uploadNewFile(this.share, length, null);
        CloudFile copyFile = this.share.getRootDirectoryReference().getFileReference(originalFile.getName() + "copyed");
        copyFile.startCopy(originalFile);

        try {
            copyFile.abortCopy(copyFile.getProperties().getCopyState().getCopyId());
            fail();
        }
        catch (StorageException e) {
            if (!e.getErrorCode().contains("NoPendingCopyOperation")) {
                throw e;
            }
        }
    }

    /**
     * Test file stream uploading.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testFileUploadFromStreamTest1() throws URISyntaxException, StorageException, IOException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(length);
        fileRef.upload(srcStream, length);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        fileRef.download(dstStream);
        FileTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = FileTestHelper.getRandomDataStream(length);
        fileRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        fileRef.download(dstStream);
        FileTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));
    }

    /**
     * Create a file and try to download a range of its contents
     * 
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testFileDownloadRangeValidationTest() throws StorageException, URISyntaxException, IOException {
        final int length = 5 * 1024 * 1024;

        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        fileRef.upload(FileTestHelper.getRandomDataStream(length), length);

        //Download full file
        fileRef.download(new ByteArrayOutputStream());
        assertEquals(length, fileRef.getProperties().getLength());

        //Download file range.
        byte[] downloadBuffer = new byte[100];
        int downloadLength = fileRef.downloadRangeToByteArray(0, (long) 100, downloadBuffer, 0);
        assertEquals(length, fileRef.getProperties().getLength());
        assertEquals(100, downloadLength);
    }

    @Test
    public void testFileUploadFromStreamTest() throws URISyntaxException, StorageException, IOException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        int length = 2 * 1024;
        ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(length);
        fileRef.upload(srcStream, length);
        ByteArrayOutputStream dstStream = new ByteArrayOutputStream();
        fileRef.download(dstStream);
        FileTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));

        length = 5 * 1024 * 1024;
        srcStream = FileTestHelper.getRandomDataStream(length);
        fileRef.upload(srcStream, length);
        dstStream = new ByteArrayOutputStream();
        fileRef.download(dstStream);
        FileTestHelper.assertStreamsAreEqual(srcStream, new ByteArrayInputStream(dstStream.toByteArray()));
    }

    @Test
    public void testFileUploadMD5Validation() throws URISyntaxException, StorageException, IOException,
            NoSuchAlgorithmException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        final int length = 4 * 1024;

        byte[] src = FileTestHelper.getRandomBuffer(length);
        ByteArrayInputStream srcStream = new ByteArrayInputStream(src);
        FileRequestOptions options = new FileRequestOptions();
        options.setDisableContentMD5Validation(false);
        options.setStoreFileContentMD5(false);

        fileRef.upload(srcStream, length, null, options, null);
        fileRef.downloadAttributes();
        fileRef.getProperties().setContentMD5("MDAwMDAwMDA=");
        fileRef.uploadProperties(null, options, null);

        try {
            fileRef.download(new ByteArrayOutputStream(), null, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(306, ex.getHttpStatusCode());
            assertEquals("InvalidMd5", ex.getErrorCode());
        }

        options.setDisableContentMD5Validation(true);
        fileRef.download(new ByteArrayOutputStream(), null, options, null);

        options.setDisableContentMD5Validation(false);
        final MessageDigest digest = MessageDigest.getInstance("MD5");
        final String calculatedMD5 = Base64.encode(digest.digest(src));

        fileRef.downloadAttributes();
        fileRef.getProperties().setContentMD5(calculatedMD5);
        fileRef.uploadProperties(null, options, null);
        fileRef.download(new ByteArrayOutputStream(), null, options, null);
        fileRef.downloadToByteArray(new byte[4096], 0);

        FileInputStream stream = fileRef.openRead();
        stream.mark(length);
        stream.read(new byte[4096]);
        try {
            HashMap<String, String> metadata = new HashMap<String, String>();
            metadata.put("a", "value");
            fileRef.setMetadata(metadata);
            fileRef.uploadMetadata();
            stream.reset();
            stream.read(new byte[4096]);
        }
        catch (IOException e) {
            assertEquals(e.getCause().getMessage(),
                    "The conditionals specified for this operation did not match server.");
        }

        final CloudFile fileRef2 = this.share.getRootDirectoryReference().getFileReference(fileName);
        assertNull(fileRef2.getProperties().getContentMD5());

        byte[] target = new byte[4];
        fileRef2.downloadRangeToByteArray(0L, 4L, target, 0);
        assertEquals(calculatedMD5, fileRef2.getProperties().getContentMD5());
    }

    @Test
    public void testFileContentMD5NewFileTest() throws URISyntaxException, StorageException, IOException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName);

        FileRequestOptions options = new FileRequestOptions();
        options.setStoreFileContentMD5(true);
        options.setDisableContentMD5Validation(false);

        File tempFile = File.createTempFile("sourceFile", ".tmp");
        file.uploadFromFile(tempFile.getAbsolutePath(), null, options, null);
    }

    /**
     * Test requesting stored content MD5 with OpenWriteExisting().
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileOpenWriteExistingWithMD5() throws URISyntaxException, StorageException, IOException {
        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef.create(512);

        FileRequestOptions options = new FileRequestOptions();
        options.setStoreFileContentMD5(true);
        options.setDisableContentMD5Validation(false);

        try
        {
            fileRef.openWriteExisting(null, options, null);
            fail("Expect failure due to requesting MD5 calculation");
        }
        catch (IllegalArgumentException e)
        {
        }
    }

    @Test
    public void testFileEmptyHeaderSigningTest() throws URISyntaxException, StorageException, IOException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        final int length = 2 * 1024;
        ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(length);

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

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                connection.setRequestProperty("x-ms-foo", "");
            }
        });

        fileRef.upload(srcStream, length, null, null, context);
        fileRef.download(new ByteArrayOutputStream(), null, null, context);
    }

    /**
     * Test downloading a file range.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testFileDownloadRangeTest() throws URISyntaxException, StorageException, IOException {
        byte[] buffer = FileTestHelper.getRandomBuffer(2 * 1024);

        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        ByteArrayInputStream wholeFile = new ByteArrayInputStream(buffer);
        file.upload(wholeFile, 2 * 1024);

        ByteArrayOutputStream fileStream = new ByteArrayOutputStream();
        try {
            file.downloadRange(0, new Long(0), fileStream);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        file.downloadRange(0, new Long(1024), fileStream);
        assertEquals(fileStream.size(), 1024);
        FileTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(fileStream.toByteArray()), wholeFile, 0,
                0, 1024, 2 * 1024);

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");
        try {
            file.downloadRange(1024, new Long(0), fileStream);
        }
        catch (IndexOutOfBoundsException ex) {

        }

        ByteArrayOutputStream fileStream2 = new ByteArrayOutputStream();
        file2.downloadRange(1024, new Long(1024), fileStream2);
        FileTestHelper.assertStreamsAreEqualAtIndex(new ByteArrayInputStream(fileStream2.toByteArray()), wholeFile,
                1024, 1024, 1024, 2 * 1024);

        FileTestHelper.assertAreEqual(file, file2);
    }

    @Test
    public void testCloudFileDownloadToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        FileTestHelper.doDownloadTest(file, 1 * 512, 2 * 512, 0);
        FileTestHelper.doDownloadTest(file, 1 * 512, 2 * 512, 1 * 512);
        FileTestHelper.doDownloadTest(file, 2 * 512, 4 * 512, 1 * 512);
        FileTestHelper.doDownloadTest(file, 5 * 1024 * 1024, 5 * 1024 * 1024, 0);
        FileTestHelper.doDownloadTest(file, 5 * 1024 * 1024, 6 * 1024 * 1024, 512);
    }

    @Test
    public void testCloudFileDownloadRangeToByteArray() throws URISyntaxException, StorageException, IOException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference(
                FileTestHelper.generateRandomFileName());

        FileTestHelper.doDownloadRangeToByteArrayTest(file, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(1 * 1024 * 1024), new Long(5 * 1024 * 1024));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 8 * 1024 * 1024, 8 * 1024 * 1024, 2 * 1024 * 1024,
                new Long(2 * 1024 * 1024), new Long(6 * 1024 * 1024));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 8 * 1024 * 1024, 8 * 1024 * 1024, 1 * 1024 * 1024,
                new Long(4 * 1024 * 1024), new Long(4 * 1024 * 1024));

        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 0, new Long(1 * 512), new Long(1 * 512));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 1 * 512, new Long(0), null);
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 1 * 512, new Long(1 * 512), null);
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 1 * 512, new Long(0), new Long(1 * 512));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                1 * 512));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 2 * 512, 4 * 512, 2 * 512, new Long(1 * 512), new Long(
                2 * 512));

        // Edge cases
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 1024, 1024, 1023, new Long(1023), new Long(1));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 1024, 1024, 0, new Long(1023), new Long(1));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 1024, 1024, 0, new Long(0), new Long(1));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 1024, 1024, 0, new Long(512), new Long(1));
        FileTestHelper.doDownloadRangeToByteArrayTest(file, 1024, 1024, 512, new Long(1023), new Long(1));
    }

    @Test
    public void testCloudFileDownloadRangeToByteArrayNegativeTest() throws URISyntaxException, StorageException,
            IOException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference(
                FileTestHelper.generateRandomFileName());
        FileTestHelper.doDownloadRangeToByteArrayNegativeTests(file);
    }

    /*
    @Test
    public void testCloudFileUploadFromStreamWithAccessCondition() throws URISyntaxException, StorageException,
            IOException {
        CloudFile file1 = share.getRootDirectoryReference().getFileReference("file1");
        AccessCondition accessCondition = AccessCondition.generateIfNoneMatchCondition("\"*\"");
        final int length = 6 * 512;
        ByteArrayInputStream srcStream = FileTestHelper.getRandomDataStream(length);
        file1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        file1.create(1024);
        accessCondition = AccessCondition.generateIfNoneMatchCondition(file1.getProperties().getEtag());
        try {
            file1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfMatchCondition(file1.getProperties().getEtag());
        file1.upload(srcStream, length, accessCondition, null, null);

        srcStream.reset();
        CloudFile file2 = share.getRootDirectoryReference().getFileReference("file2");
        file2.create(1024);
        accessCondition = AccessCondition.generateIfMatchCondition(file1.getProperties().getEtag());
        try {
            file1.upload(srcStream, length, accessCondition, null, null);
        }
        catch (StorageException ex) {
            Assert.assertEquals(HttpURLConnection.HTTP_PRECON_FAILED, ex.getHttpStatusCode());
        }

        srcStream.reset();
        accessCondition = AccessCondition.generateIfNoneMatchCondition(file2.getProperties().getEtag());
        file1.upload(srcStream, length, accessCondition, null, null);
    }
    */

    /**
     * Test file input stream.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    public void testCloudFileInputStream() throws URISyntaxException, StorageException, IOException {
        final int fileLength = 16 * 1024;
        final Random randGenerator = new Random();
        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        final byte[] buff = new byte[fileLength];
        randGenerator.nextBytes(buff);
        buff[0] = -1;
        buff[1] = -128;
        final ByteArrayInputStream sourceStream = new ByteArrayInputStream(buff);

        final FileRequestOptions options = new FileRequestOptions();
        final OperationContext operationContext = new OperationContext();
        options.setTimeoutIntervalInMs(90000);
        options.setRetryPolicyFactory(new RetryNoRetry());
        fileRef.upload(sourceStream, fileLength, null, options, operationContext);

        com.microsoft.azure.storage.file.FileInputStream fileStream = fileRef.openRead();

        for (int i = 0; i < fileLength; i++) {
            int data = fileStream.read();
            assertTrue(data >= 0);
            assertEquals(buff[i], (byte) data);
        }

        assertEquals(-1, fileStream.read());

        fileRef.delete();
    }

    /**
     * Test file uploading from byte arrays.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileUploadFromByteArray() throws Exception {
        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName);

        this.doUploadFromByteArrayTest(file, 4 * 512, 0, 4 * 512);
        this.doUploadFromByteArrayTest(file, 4 * 512, 0, 2 * 512);
        this.doUploadFromByteArrayTest(file, 4 * 512, 1 * 512, 2 * 512);
        this.doUploadFromByteArrayTest(file, 4 * 512, 2 * 512, 2 * 512);
    }

    /**
     * Test file upload and download using text.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileUploadDownloadFromText() throws IOException, StorageException, URISyntaxException {
        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName);
        String textFile = "string of text to upload to a file. The_quick_brown_fox_jumps_over_the_lazy_dog. 1!\". &quot.";
        file.uploadText(textFile);
        String result = file.downloadText();
        assertEquals(textFile, result);
    }

    /**
     * Test file upload and download using io files.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileUploadDownloadFromFile() throws IOException, StorageException, URISyntaxException {
        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName);

        this.doUploadDownloadFileTest(file, 512);
        this.doUploadDownloadFileTest(file, 4096);
        this.doUploadDownloadFileTest(file, 5 * 1024 * 1024);
        this.doUploadDownloadFileTest(file, 11 * 1024 * 1024);
    }

    private void doUploadFromByteArrayTest(CloudFile file, int bufferSize, int bufferOffset, int count)
            throws Exception {
        byte[] buffer = FileTestHelper.getRandomBuffer(bufferSize);
        byte[] downloadedBuffer = new byte[bufferSize];

        file.uploadFromByteArray(buffer, bufferOffset, count);
        file.downloadToByteArray(downloadedBuffer, 0);

        int i = 0;
        for (; i < count; i++) {
            assertEquals(buffer[i + bufferOffset], downloadedBuffer[i]);
        }

        for (; i < downloadedBuffer.length; i++) {
            assertEquals(0, downloadedBuffer[i]);
        }
    }

    private void doUploadDownloadFileTest(CloudFile file, int fileSize) throws IOException, StorageException, URISyntaxException {
        File sourceFile = File.createTempFile("sourceFile", ".tmp");
        File destinationFile = new File(sourceFile.getParentFile(), "destinationFile.tmp");

        try {
            byte[] buffer = FileTestHelper.getRandomBuffer(fileSize);
            java.io.FileOutputStream fos = new java.io.FileOutputStream(sourceFile);
            fos.write(buffer);
            fos.close();
            file.uploadFromFile(sourceFile.getAbsolutePath());

            file.downloadToFile(destinationFile.getAbsolutePath());
            assertTrue("Destination file does not exist.", destinationFile.exists());
            assertEquals("Destination file does not match input file.", fileSize, destinationFile.length());
            java.io.FileInputStream fis = new java.io.FileInputStream(destinationFile);

            byte[] readBuffer = new byte[fileSize];
            fis.read(readBuffer);
            fis.close();

            for (int i = 0; i < fileSize; i++) {
                assertEquals("File contents do not match.", buffer[i], readBuffer[i]);
            }
        }
        finally {
            if (sourceFile.exists()) {
                sourceFile.delete();
            }

            if (destinationFile.exists()) {
                destinationFile.delete();
            }
        }
    }

    /**
     * Test file range uploads.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileUploadRange() throws URISyntaxException, StorageException, IOException {
        int fileLengthToUse = 8 * 512;
        byte[] buffer = FileTestHelper.getRandomBuffer(8 * 512);

        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef.create(fileLengthToUse);

        FileRequestOptions options = new FileRequestOptions();
        options.setUseTransactionalContentMD5(true);

        // Upload one page (page 0)
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        fileRef.uploadRange(inputStream, 0, 512, null, options, null);

        // Upload pages 2-4
        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        fileRef.uploadRange(inputStream, 2 * 512, 3 * 512, null, options, null);

        // Now, we expect the first 512 bytes of the file to be the first 512 bytes of the random buffer (page 0)
        // the next 512 bytes should be 0 (page 1)
        // The next 3 * 512 bytes should be equal to bytes (512 -> 4 * 512) of the random buffer (pages 2-4)
        // The next 3 * 512 bytes should be 0 (pages 5-7)

        byte[] result = new byte[fileLengthToUse];
        fileRef.downloadToByteArray(result, 0);

        for (int i = 0; i < 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (int i = 0; i < 512; i++) {
            assertEquals(0, result[i + 512]);
        }

        for (int i = 0; i < 3 * 512; i++) {
            assertEquals(buffer[i + 512], result[i + 2 * 512]);
        }

        for (int i = 0; i < 3 * 512; i++) {
            assertEquals(0, result[i + 5 * 512]);
        }

        inputStream = new ByteArrayInputStream(buffer);
    }
    
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testVerifyTransactionalMD5ValidationMissingOverallMD5() throws URISyntaxException, StorageException, IOException {
        final String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);

        final int length = 3*1024;
        ByteArrayInputStream srcStream = BlobTestHelper.getRandomDataStream(length);
        FileRequestOptions options = new FileRequestOptions();
        options.setDisableContentMD5Validation(true);
        options.setStoreFileContentMD5(false);

        fileRef.upload(srcStream, length, null, options, null);

        options.setDisableContentMD5Validation(false);
        options.setStoreFileContentMD5(true);
        options.setUseTransactionalContentMD5(true);
        final CloudFile fileRef2 = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef2.downloadRange(1024, (long)1024, new ByteArrayOutputStream(), null, options, null);
        assertNull(fileRef2.getProperties().getContentMD5());
    }

    /**
     * Test clearing file ranges.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileClearRange() throws URISyntaxException, StorageException, IOException {
        int fileLengthToUse = 8 * 512;
        byte[] buffer = FileTestHelper.getRandomBuffer(8 * 512);

        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef.create(fileLengthToUse);

        // Upload one page (page 0)
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        fileRef.uploadRange(inputStream, 0, fileLengthToUse);

        fileRef.clearRange(3 * 512, 2 * 512);

        byte[] result = new byte[fileLengthToUse];
        fileRef.downloadToByteArray(result, 0);

        int i = 0;

        for (; i < 3 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (; i < 5 * 512; i++) {
            assertEquals(0, result[i]);
        }

        for (; i < 8 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }
    }

    /**
     * Test file resizing.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileResize2() throws StorageException, URISyntaxException {
        CloudFile file = this.share.getRootDirectoryReference().getFileReference("file1");
        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference("file1");

        file.create(1024);
        assertEquals(1024, file.getProperties().getLength());

        file2.downloadAttributes();
        assertEquals(1024, file2.getProperties().getLength());

        file2.getProperties().setContentType("text/plain");
        file2.uploadProperties();

        file.resize(2048);
        assertEquals(2048, file.getProperties().getLength());

        file.downloadAttributes();
        assertEquals("text/plain", file.getProperties().getContentType());

        file2.downloadAttributes();
        assertEquals(2048, file2.getProperties().getLength());
    }

    /**
     * Test file range downloading.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileDownloadRange() throws StorageException, URISyntaxException, IOException {
        int fileLengthToUse = 8 * 512;
        byte[] buffer = FileTestHelper.getRandomBuffer(8 * 512);

        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef.create(fileLengthToUse);

        // Upload one page (page 0)
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
        fileRef.uploadRange(inputStream, 0, 512);

        // Upload pages 2-4
        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        fileRef.uploadRange(inputStream, 2 * 512, 3 * 512);

        // Now, we expect the first 512 bytes of the file to be the first 512 bytes of the random buffer (page 0)
        // the next 512 bytes should be 0 (page 1)
        // The next 3 * 512 bytes should be equal to bytes (512 -> 4 * 512) of the random buffer (pages 2-4)
        // The next 3 * 512 bytes should be 0 (pages 5-7)

        ArrayList<FileRange> actualFileRanges = fileRef.downloadFileRanges();
        ArrayList<FileRange> expectedFileRanges = new ArrayList<FileRange>();
        expectedFileRanges.add(new FileRange(0, 512 - 1));
        expectedFileRanges.add(new FileRange(2 * 512, 2 * 512 + 3 * 512 - 1));

        assertEquals(expectedFileRanges.size(), actualFileRanges.size());
        for (int i = 0; i < expectedFileRanges.size(); i++) {
            assertEquals(expectedFileRanges.get(i).getStartOffset(), actualFileRanges.get(i).getStartOffset());
            assertEquals(expectedFileRanges.get(i).getEndOffset(), actualFileRanges.get(i).getEndOffset());
        }
    }

    /**
     * Test downloadAttributes.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileUploadDownloadFileProperties() throws URISyntaxException, StorageException, IOException {
        final int length = 512;

        // do this to make sure the set MD5 can be compared without an exception being thrown
        FileRequestOptions options = new FileRequestOptions();
        options.setDisableContentMD5Validation(true);

        // with explicit upload/download of properties 
        String fileName1 = FileTestHelper.generateRandomFileName();
        CloudFile fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1);

        fileRef1.upload(FileTestHelper.getRandomDataStream(length), length);

        FileTestHelper.setFileProperties(fileRef1);
        FileProperties props1 = fileRef1.getProperties();
        fileRef1.uploadProperties();

        fileRef1.downloadAttributes(null, options, null);
        FileProperties props2 = fileRef1.getProperties();

        FileTestHelper.assertAreEqual(props1, props2);

        // by uploading/downloading the file   
        fileName1 = FileTestHelper.generateRandomFileName();
        fileRef1 = this.share.getRootDirectoryReference().getFileReference(fileName1);

        FileTestHelper.setFileProperties(fileRef1);
        props1 = fileRef1.getProperties();

        fileRef1.upload(FileTestHelper.getRandomDataStream(length), length);

        fileRef1.download(new ByteArrayOutputStream(), null, options, null);
        props2 = fileRef1.getProperties();

        FileTestHelper.assertAreEqual(props1, props2);
    }

    /**
     * Test FileOutputStream.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileOpenOutputStream() throws URISyntaxException, StorageException, IOException {
        int fileLengthToUse = 8 * 512;
        byte[] buffer = FileTestHelper.getRandomBuffer(8 * 512);

        String fileName = FileTestHelper.generateRandomFileName();
        final CloudFile fileRef = this.share.getRootDirectoryReference().getFileReference(fileName);
        fileRef.create(fileLengthToUse);

        FileOutputStream fileOutputStream = fileRef.openWriteNew(fileLengthToUse);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);

        fileOutputStream = fileRef.openWriteNew(fileLengthToUse);
        inputStream = new ByteArrayInputStream(buffer);
        fileOutputStream.write(inputStream, 512);

        inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512);
        fileOutputStream.write(inputStream, 3 * 512);

        fileOutputStream.close();

        byte[] result = new byte[fileLengthToUse];
        fileRef.downloadToByteArray(result, 0);

        int i = 0;
        for (; i < 4 * 512; i++) {
            assertEquals(buffer[i], result[i]);
        }

        for (; i < 8 * 512; i++) {
            assertEquals(0, result[i]);
        }
    }

    /**
     * Test FileOutputStream.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     */
    @Test
    public void testCloudFileOpenOutputStreamNoArgs() throws URISyntaxException, StorageException {
        String fileName = FileTestHelper.generateRandomFileName();
        CloudFile file = this.share.getRootDirectoryReference().getFileReference(fileName);

        try {
            file.openWriteExisting();
        }
        catch (StorageException ex) {
            assertEquals("The specified resource does not exist.", ex.getMessage());
            assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ex.getHttpStatusCode());
        }

        file.openWriteNew(1024);
        file.openWriteExisting();

        CloudFile file2 = this.share.getRootDirectoryReference().getFileReference(fileName);
        file2.downloadAttributes();
        assertEquals(1024, file2.getProperties().getLength());
    }

    /**
     * Test specific deleteIfExists case.
     * 
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudFileDeleteIfExistsErrorCode() throws URISyntaxException, StorageException, IOException {
        CloudFileClient client = FileTestHelper.createCloudFileClient();
        CloudFileShare share = client.getShareReference(FileTestHelper.generateRandomShareName());
        share.create();
        CloudFileDirectory directory = share.getRootDirectoryReference().getDirectoryReference("directory");
        directory.create();
        final CloudFile file = directory.getFileReference("file");

        try {
            file.delete();
            fail("File should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.RESOURCE_NOT_FOUND, e.getErrorCode());
        }

        assertFalse(file.exists());
        assertFalse(file.deleteIfExists());

        file.create(2);
        assertTrue(file.exists());

        assertTrue(file.deleteIfExists());
        assertFalse(file.deleteIfExists());

        // check if second condition works in delete if exists
        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

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

        // The second delete of a file will return a 404
        file.create(2);
        assertFalse(file.deleteIfExists(null, null, ctx));
    }
    
    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testFileNamePlusEncoding() throws StorageException, URISyntaxException, IOException,
            InterruptedException {
        CloudFile originalFile = FileTestHelper.uploadNewFile(this.share, 1024 /* length */, null);
        CloudFile copyFile = this.share.getRootDirectoryReference().getFileReference(originalFile.getName() + "copyed");

        copyFile.startCopy(originalFile);
        FileTestHelper.waitForCopy(copyFile);
        
        copyFile.downloadAttributes();
        originalFile.downloadAttributes();
        FileProperties prop1 = copyFile.getProperties();
        FileProperties prop2 = originalFile.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(), prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testFileGetRangeContentMD5Bounds() throws StorageException, IOException, URISyntaxException {
        {
            CloudFile file = FileTestHelper.uploadNewFile(this.share, 5 * Constants.MB, null);

            FileRequestOptions options = new FileRequestOptions();
            OperationContext opContext = new OperationContext();
            try {
                FileRequest.getFile(file.getUri(), options, opContext, null, null, 0L, 4L * Constants.MB, true);
                FileRequest.getFile(file.getUri(), options, opContext, null, null, 0L, 4L * Constants.MB + 1, true);
                fail("The request for range ContentMD5 should have thrown an Exception for exceeding the limit.");
            }
            catch (IllegalArgumentException e)
            {
                assertEquals(e.getMessage(), String.format("The value of the parameter 'count' should be between 1 and %1d.", Constants.MAX_BLOCK_SIZE));
            }
        }
    }
    
    private CloudFile doCloudBlobCopy(CloudBlob source, int length) throws Exception {
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        cal.setTime(new Date());
        cal.add(Calendar.MINUTE, 5);
        
        // Source SAS must have read permissions
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
        policy.setSharedAccessExpiryTime(cal.getTime());

        String sasToken = source.generateSharedAccessSignature(policy, null, null);

        // Get destination reference
        final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination");
        
        // Start copy and wait for completion
        StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
        Constructor<? extends CloudBlob> blobType = source.getClass().getConstructor(URI.class);
        String copyId = destination.startCopy(blobType.newInstance(credentials.transformUri(source.getUri())));
        FileTestHelper.waitForCopy(destination);
        destination.downloadAttributes();
        
        // Check original file references for equality
        assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus());
        assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(),
                destination.getCopyState().getSource().getPath());
        assertEquals(length, destination.getCopyState().getTotalBytes().intValue());
        assertEquals(length, destination.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, destination.getProperties().getCopyState().getCopyId());

        // Attempt to abort the completed copy operation.
        try {
            destination.abortCopy(destination.getCopyState().getCopyId());
            FileTestHelper.waitForCopy(destination);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
        }

        assertNotNull(destination.getProperties().getEtag());
        assertFalse(source.getProperties().getEtag().equals(destination.getProperties().getEtag()));

        source.downloadAttributes();
        FileProperties prop1 = destination.getProperties();
        BlobProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", destination.getMetadata().get("Test"));
        return destination;
    }

    private void doCloudFileCopy(boolean sourceIsSas, boolean destinationIsSas)
            throws URISyntaxException, StorageException, IOException, InvalidKeyException, InterruptedException {
        // Create source on server.
        final CloudFile source = this.share.getRootDirectoryReference().getFileReference("source");

        final String data = "String data";
        source.getMetadata().put("Test", "value");
        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        cal.setTime(new Date());
        cal.add(Calendar.MINUTE, 5);

        CloudFile copySource = source;
        if (sourceIsSas) {
            // Source SAS must have read permissions
            SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
            policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ));
            policy.setSharedAccessExpiryTime(cal.getTime());

            String sasToken = source.generateSharedAccessSignature(policy, null, null);

            // Get source file reference
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            copySource = new CloudFile(credentials.transformUri(source.getUri()));
        }

        // Get destination reference
        final CloudFile destination = this.share.getRootDirectoryReference().getFileReference("destination");

        CloudFile copyDestination = destination;
        if (destinationIsSas) {
            // Destination SAS must have write permissions
            SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
            policy.setPermissions(EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE));
            policy.setSharedAccessExpiryTime(cal.getTime());

            String sasToken = destination.generateSharedAccessSignature(policy, null, null);

            // Get destination file reference
            StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sasToken);
            copyDestination = new CloudFile(destination.getUri(), credentials);
        }

        // Start copy and wait for completion
        String copyId = copyDestination.startCopy(copySource);
        FileTestHelper.waitForCopy(copyDestination);
        destination.downloadAttributes();

        // Check original file references for equality
        assertEquals(CopyStatus.SUCCESS, destination.getCopyState().getStatus());
        assertEquals(source.getServiceClient().getCredentials().transformUri(source.getUri()).getPath(),
                destination.getCopyState().getSource().getPath());
        assertEquals(data.length(), destination.getCopyState().getTotalBytes().intValue());
        assertEquals(data.length(), destination.getCopyState().getBytesCopied().intValue());
        assertEquals(copyId, destination.getProperties().getCopyState().getCopyId());

        if (!destinationIsSas) {
            try {
                copyDestination.abortCopy(destination.getCopyState().getCopyId());
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_CONFLICT, ex.getHttpStatusCode());
            }
        }

        assertNotNull(destination.getProperties().getEtag());
        assertFalse(source.getProperties().getEtag().equals(destination.getProperties().getEtag()));

        String copyData = destination.downloadText(Constants.UTF8_CHARSET, null, null, null);
        assertEquals(data, copyData);

        FileProperties prop1 = destination.getProperties();
        FileProperties prop2 = source.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", destination.getMetadata().get("Test"));
        
        destination.delete();
        source.delete();
    }

    @Test
    public void testUnsupportedFileApisWithinShareSnapshot() throws StorageException, URISyntaxException {
        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFile file = snapshot.getRootDirectoryReference().getFileReference("file");

        try {
            file.create(1024);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.delete();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.uploadMetadata();
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.abortCopy(null);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.clearRange(0, 512);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.startCopy(file);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        }
        try {
            file.upload(null, 512);
            fail("Shouldn't get here");
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.INVALID_OPERATION_FOR_A_SHARE_SNAPSHOT, e.getMessage());
        } catch (IOException e) {
            fail("Shouldn't get here");
        }

        snapshot.delete();
    }

    @Test
    public void testSupportedFileApisInShareSnapshot() throws StorageException, URISyntaxException, UnsupportedEncodingException {
        CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dir1");
        dir.deleteIfExists();
        dir.create();
        CloudFile file = dir.getFileReference("file");
        file.create(1024);

        HashMap<String, String> meta = new HashMap<String, String>();
        meta.put("key1", "value1");
        file.setMetadata(meta);
        file.uploadMetadata();

        CloudFileShare snapshot = this.share.createSnapshot();
        CloudFile snapshotFile = snapshot.getRootDirectoryReference()
                                         .getDirectoryReference("dir1").getFileReference("file");

        HashMap<String, String> meta2 = new HashMap<String, String>();
        meta2.put("key2", "value2");
        file.setMetadata(meta2);
        file.uploadMetadata();
        snapshotFile.downloadAttributes();
        
        assertTrue(snapshotFile.getMetadata().size() == 1 && snapshotFile.getMetadata().get("key1").equals("value1"));
        assertNotNull(snapshotFile.getProperties().getEtag());

        file.downloadAttributes();
        assertTrue(file.getMetadata().size() == 1 && file.getMetadata().get("key2").equals("value2"));
        assertNotNull(file.getProperties().getEtag());
        assertNotEquals(file.getProperties().getEtag(), snapshotFile.getProperties().getEtag());

        final UriQueryBuilder uriBuilder = new UriQueryBuilder();
        uriBuilder.add("sharesnapshot", snapshot.snapshotID);
        CloudFile snapshotFile2 = new CloudFile(uriBuilder.addToURI(file.getUri()), this.share.getServiceClient().getCredentials());
        assertEquals(snapshot.snapshotID, snapshotFile2.getShare().snapshotID);
        assertTrue(snapshotFile2.exists());
        
        snapshot.delete();
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class FileSasTests {

    protected static CloudFileClient fileClient = null;
    protected CloudFileShare share;
    protected CloudFile file;

    @Before
    public void fileSASTestMethodSetUp() throws URISyntaxException, StorageException, IOException {
        if (fileClient == null) {
            fileClient = TestHelper.createCloudFileClient();
        }
        this.share = FileTestHelper.getRandomShareReference();
        this.share.create();

        this.file = (CloudFile) FileTestHelper.uploadNewFile(this.share, 100, null);
    }

    @After
    public void fileSASTestMethodTearDown() throws StorageException {
        this.share.deleteIfExists();
    }

    @Test
    public void testApiVersion() throws InvalidKeyException, StorageException, URISyntaxException {
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE,
                        SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300);
        String sas = this.file.generateSharedAccessSignature(policy, null);
        
        // should not be appended before signing
        assertEquals(-1, sas.indexOf(Constants.QueryConstants.API_VERSION));
        
        OperationContext ctx = new OperationContext();
        ctx.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() {

            @Override
            public void eventOccurred(ResponseReceivedEvent eventArg) {
                // should be appended after signing
                HttpURLConnection conn = (HttpURLConnection) eventArg.getConnectionObject();
                assertTrue(conn.getURL().toString().indexOf(Constants.QueryConstants.API_VERSION) != -1);
            }
        });

        CloudFile sasFile = new CloudFile(new URI(this.file.getUri().toString() + "?" + sas));
        sasFile.uploadMetadata(null, null, ctx);
    }

    @Test
    public void testDirectorySas() throws InvalidKeyException, IllegalArgumentException, StorageException,
            URISyntaxException, InterruptedException {
        CloudFileDirectory dir = this.share.getRootDirectoryReference().getDirectoryReference("dirFile");
        CloudFile file = dir.getFileReference("dirFile");

        dir.create();
        file.create(512);

        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300);

        // Test directory SAS with a file SAS token from an identically named file
        String sas = file.generateSharedAccessSignature(policy, null);
        CloudFileDirectory sasDir = new CloudFileDirectory(new URI(dir.getUri().toString() + "?" + sas));
        try {
            sasDir.downloadAttributes();
            fail("This should result in an authentication error.");
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
        }

        // Test directory SAS with a share SAS token
        sas = this.share.generateSharedAccessSignature(policy, null);
        sasDir = new CloudFileDirectory(new URI(dir.getUri().toString() + "?" + sas));
        sasDir.downloadAttributes();

        // Test deleting a directory using a SAS token. The directory must be empty for this request to succeed.
        file.delete();
        sasDir.delete();
    }

    @Test
    @Category({ SecondaryTests.class })
    public void testIpAcl()
            throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException, UnknownHostException {
        
        // Generate policies
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300);
        IPRange range1 = new IPRange("0.0.0.0", "255.255.255.255");
        IPRange range2 = new IPRange("0.0.0.0");
        
        // Ensure access attempt from invalid IP fails
        IPRange sourceIP = null;
        try {
            String shareSasNone = this.share.generateSharedAccessSignature(policy, null, range2, null);
            CloudFileShare noneShare =
                    new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), shareSasNone));
    
            assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                    noneShare.getServiceClient().getCredentials().getClass().toString());
    
            CloudFile noneFile = noneShare.getRootDirectoryReference().getFileReference(this.file.getName());
            noneFile.download(new ByteArrayOutputStream());
            fail();
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            
            final String[] words = ex.getMessage().split(" ");
            // final word
            String lastWord = words[words.length - 1];
            // strip trailing period
            lastWord = lastWord.substring(0, lastWord.length() - 1);
            
            sourceIP = new IPRange(lastWord);
        }
        
        // Ensure access attempt from the single allowed IP succeeds
        String shareSasOne = this.share.generateSharedAccessSignature(policy, null, sourceIP, null);
        CloudFileShare oneShare =
                new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), shareSasOne));

        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                oneShare.getServiceClient().getCredentials().getClass().toString());

        CloudFile oneFile = oneShare.getRootDirectoryReference().getFileReference(this.file.getName());
        oneFile.download(new ByteArrayOutputStream());

        // Ensure access attempt from one of many valid IPs succeeds
        String shareSasAll = this.share.generateSharedAccessSignature(policy, null, range1, null);
        CloudFileShare allShare =
                new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), shareSasAll));

        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                allShare.getServiceClient().getCredentials().getClass().toString());

        CloudFile allFile = allShare.getRootDirectoryReference().getFileReference(this.file.getName());
        allFile.download(new ByteArrayOutputStream());
    }

    @Test
    @Category({ SecondaryTests.class })
    public void testProtocolRestrictions()
            throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException {
        
        // Generate policy
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300);
        
        // Generate share SAS and URI
        String shareSasHttps =
                this.share.generateSharedAccessSignature(policy, null, null, SharedAccessProtocols.HTTPS_ONLY);
        String shareSasHttp =
                this.share.generateSharedAccessSignature(policy, null, null, SharedAccessProtocols.HTTPS_HTTP);
        final URI httpUri = new URI("http", this.share.getUri().getAuthority(), this.share.getUri().getPath(), 
        		this.share.getUri().getQuery(), null);
        final URI httpsUri = new URI("https", this.share.getUri().getAuthority(), this.share.getUri().getPath(), 
        		this.share.getUri().getQuery(), null);
        
        // Ensure attempt from http fails against HTTPS_ONLY
        try {
            CloudFileShare httpShare =
                    new CloudFileShare(PathUtility.addToQuery(httpUri, shareSasHttps));
            assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                    httpShare.getServiceClient().getCredentials().getClass().toString());
            CloudFile httpFile = httpShare.getRootDirectoryReference().getFileReference(this.file.getName());
            httpFile.download(new ByteArrayOutputStream());
            
            fail();
        }
        catch (StorageException ex) {
            assertEquals(Constants.HeaderConstants.HTTP_UNUSED_306, ex.getHttpStatusCode());
            assertEquals(SR.CANNOT_TRANSFORM_NON_HTTPS_URI_WITH_HTTPS_ONLY_CREDENTIALS, ex.getMessage());
        }

        // Ensure attempt from https succeeds against HTTPS_ONLY
        CloudFileShare httpsShare =
                new CloudFileShare(PathUtility.addToQuery(httpsUri, shareSasHttps));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpsShare.getServiceClient().getCredentials().getClass().toString());
        CloudFile httpsFile = httpsShare.getRootDirectoryReference().getFileReference(this.file.getName());
        httpsFile.download(new ByteArrayOutputStream());
        
        // Ensure attempt from both http and https succeed against HTTPS_HTTP
        CloudFileShare httpShare =
                new CloudFileShare(PathUtility.addToQuery(httpUri, shareSasHttp));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpShare.getServiceClient().getCredentials().getClass().toString());
        CloudFile httpFile = httpShare.getRootDirectoryReference().getFileReference(this.file.getName());
        httpFile.download(new ByteArrayOutputStream());
        
        httpsShare =
                new CloudFileShare(PathUtility.addToQuery(httpsUri, shareSasHttp));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                httpsShare.getServiceClient().getCredentials().getClass().toString());
        httpsFile = httpsShare.getRootDirectoryReference().getFileReference(this.file.getName());
        httpsFile.download(new ByteArrayOutputStream());
    }

    @Test
    @Category({ SecondaryTests.class, SlowTests.class })
    public void testShareSAS() throws IllegalArgumentException, StorageException, URISyntaxException,
            InvalidKeyException, InterruptedException {
        SharedAccessFilePolicy policy1 = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE,
                SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 300);
        SharedAccessFilePolicy policy2 = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300);
        FileSharePermissions permissions = new FileSharePermissions();
    
        permissions.getSharedAccessPolicies().put("full", policy1);
        permissions.getSharedAccessPolicies().put("readlist", policy2);
        this.share.uploadPermissions(permissions);
        Thread.sleep(30000);
    
        String shareReadListSas = this.share.generateSharedAccessSignature(policy2, null);
        CloudFileShare readListShare =
                new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), shareReadListSas));
    
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                readListShare.getServiceClient().getCredentials().getClass().toString());
    
        CloudFile fileFromSasShare = readListShare.getRootDirectoryReference().getFileReference(this.file.getName());
        fileFromSasShare.download(new ByteArrayOutputStream());

        // do not give the client and check that the new share's client has the correct perms
        CloudFileShare shareFromUri = new CloudFileShare(PathUtility.addToQuery(
                readListShare.getStorageUri(), this.share.generateSharedAccessSignature(null, "readlist")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                shareFromUri.getServiceClient().getCredentials().getClass().toString());

        // create credentials from sas
        StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
                this.share.generateSharedAccessSignature(null, "readlist"));
        CloudFileClient client = new CloudFileClient(this.share.getServiceClient().getStorageUri(), creds);

        CloudFileShare shareFromClient = client.getShareReference(readListShare.getName());
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                shareFromClient.getServiceClient().getCredentials().getClass().toString());
        assertEquals(client, shareFromClient.getServiceClient());
    }

    @Test
    @Category({ SlowTests.class })
    public void testShareUpdateSAS()
            throws InvalidKeyException, StorageException, IOException, URISyntaxException, InterruptedException {
        // Create a policy with read/write access and get SAS.
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE), 300);
        FileSharePermissions permissions = new FileSharePermissions();

        permissions.getSharedAccessPolicies().put("readwrite", policy);
        this.share.uploadPermissions(permissions);
        Thread.sleep(30000);

        String sasToken = this.share.generateSharedAccessSignature(policy, null);

        CloudFile file = FileTestHelper.uploadNewFile(this.share, 64, null);
        testAccess(sasToken,
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE), this.share, file);

        //Change the policy to only read and update SAS.
        SharedAccessFilePolicy policy2 = createSharedAccessPolicy(EnumSet.of(SharedAccessFilePermissions.READ), 300);
        permissions = new FileSharePermissions();

        permissions.getSharedAccessPolicies().put("read", policy2);
        this.share.uploadPermissions(permissions);
        Thread.sleep(30000);

        // Extra check to make sure that we have actually updated the SAS token.
        String sasToken2 = this.share.generateSharedAccessSignature(policy2, null);
        CloudFileShare sasShare = new CloudFileShare(PathUtility.addToQuery(this.share.getUri(), sasToken2));

        try {
            FileTestHelper.uploadNewFile(sasShare, 64, null);
            Assert.fail();
        }
        catch (StorageException ex) {
            assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
        }
    }

    @Test
    @Category({ SlowTests.class })
    public void testShareSASCombinations()
            throws StorageException, URISyntaxException, IOException, InvalidKeyException, InterruptedException {
        EnumSet<SharedAccessFilePermissions> permissionSet = null;
        Map<Integer, CloudFileShare> shares = new HashMap<Integer, CloudFileShare>();

        try {
            for (int bits = 0x1; bits < 0x40; bits++) {
                shares.put(bits, FileTestHelper.getRandomShareReference());
                shares.get(bits).createIfNotExists();

                permissionSet = getPermissions(bits);
                FileSharePermissions perms = new FileSharePermissions();

                perms.getSharedAccessPolicies().put("readwrite" + bits, createSharedAccessPolicy(permissionSet, 300));
                shares.get(bits).uploadPermissions(perms);
            }

            Thread.sleep(30000);

            for (int bits = 0x1; bits < 0x20; bits++) {
                permissionSet = getPermissions(bits);
                String sasToken = shares.get(bits).generateSharedAccessSignature(null, "readwrite" + bits);

                CloudFile testFile = FileTestHelper.uploadNewFile(shares.get(bits), 64, null);
                testAccess(sasToken, permissionSet, shares.get(bits), testFile);
            }
        }
        finally {
            for (int bits = 0x1; bits < shares.size(); bits++) {
                shares.get(bits).deleteIfExists();
            }
        }
    }

    @Test
    public void testFileSASCombinations() throws URISyntaxException, StorageException, InvalidKeyException, IOException {
        EnumSet<SharedAccessFilePermissions> permissionSet = null;

        for (int bits = 0x1; bits < 0x10; bits++) {
            permissionSet = getPermissions(bits);
            SharedAccessFilePolicy policy = createSharedAccessPolicy(permissionSet, 300);

            CloudFile testFile = FileTestHelper.uploadNewFile(this.share, 512, null);
            String sasToken = testFile.generateSharedAccessSignature(policy, null, null);

            testAccess(sasToken, permissionSet, null, testFile);
        }
    }

    @Test
    public void testFileSAS() throws InvalidKeyException, IllegalArgumentException, StorageException,
            URISyntaxException, InterruptedException {
        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.LIST), 300);
        FileSharePermissions perms = new FileSharePermissions();

        perms.getSharedAccessPolicies().put("readperm", policy);
        this.share.uploadPermissions(perms);
        Thread.sleep(30000);

        CloudFile sasFile = new CloudFile(
                new URI(this.file.getUri().toString() + "?" + this.file.generateSharedAccessSignature(null, "readperm")));
        sasFile.download(new ByteArrayOutputStream());

        // do not give the client and check that the new file's client has the correct permissions
        CloudFile fileFromUri = new CloudFile(PathUtility.addToQuery(this.file.getStorageUri(),
                this.file.generateSharedAccessSignature(null, "readperm")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                fileFromUri.getServiceClient().getCredentials().getClass().toString());
        
        // create credentials from sas
        StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
                this.file.generateSharedAccessSignature(policy, null, null));
        CloudFileClient client = new CloudFileClient(sasFile.getServiceClient().getStorageUri(), creds);

        CloudFile fileFromClient = client.getShareReference(this.file.getShare().getName()).getRootDirectoryReference()
                .getFileReference(this.file.getName());
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(),
                fileFromClient.getServiceClient().getCredentials().getClass().toString());
        assertEquals(client, fileFromClient.getServiceClient());
    }

    @Test
    public void testFileSASWithSharedAccessFileHeaders() throws InvalidKeyException, IllegalArgumentException,
            StorageException, URISyntaxException, InterruptedException {
        SharedAccessFilePolicy policy = createSharedAccessPolicy(EnumSet.of(SharedAccessFilePermissions.READ,
                SharedAccessFilePermissions.WRITE, SharedAccessFilePermissions.LIST), 300);
        FileSharePermissions perms = new FileSharePermissions();

        perms.getSharedAccessPolicies().put("rwperm", policy);
        this.share.uploadPermissions(perms);
        Thread.sleep(30000);

        SharedAccessFileHeaders headers = new SharedAccessFileHeaders();
        headers.setCacheControl("no-cache");
        headers.setContentDisposition("attachment; filename=\"fname.ext\"");
        headers.setContentEncoding("gzip");
        headers.setContentLanguage("da");
        headers.setContentType("text/html; charset=utf-8");

        CloudFile sasFile = new CloudFile(
                new URI(this.file.getUri().toString() + "?" + this.file.generateSharedAccessSignature(null, headers, "rwperm")));
        OperationContext context = new OperationContext();

        context.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {
            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject();
                assertEquals("no-cache", connection.getHeaderField(Constants.HeaderConstants.CACHE_CONTROL));
                assertEquals("attachment; filename=\"fname.ext\"",
                        connection.getHeaderField(Constants.HeaderConstants.CONTENT_DISPOSITION));
                assertEquals("gzip", connection.getHeaderField(Constants.HeaderConstants.CONTENT_ENCODING));
                assertEquals("da", connection.getHeaderField(Constants.HeaderConstants.CONTENT_LANGUAGE));
                assertEquals("text/html; charset=utf-8",
                        connection.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE));
            }
        });

        sasFile.download(new ByteArrayOutputStream(), null, null, context);
    }
    
    @Test
    public void testFileCopyFromBlobWithSasAndSnapshot()
            throws URISyntaxException, StorageException, InterruptedException, IOException, InvalidKeyException {
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudBlobContainer container = TestHelper.createCloudBlobClient().getContainerReference(BlobTestHelper.generateRandomContainerName());
        container.createIfNotExists();
        CloudBlockBlob source = container.getBlockBlobReference(blobName);
        String data = "String data";
        source.uploadText(data, Constants.UTF8_CHARSET, null, null, null);

        byte[] buffer = BlobTestHelper.getRandomBuffer(512);
        ByteArrayInputStream stream = new ByteArrayInputStream(buffer);
        source.upload(stream, buffer.length);
        source.getMetadata().put("Test", "value");
        source.uploadMetadata();

        SharedAccessFilePolicy policy = createSharedAccessPolicy(
                EnumSet.of(SharedAccessFilePermissions.READ, SharedAccessFilePermissions.WRITE,
                      SharedAccessFilePermissions.LIST, SharedAccessFilePermissions.DELETE), 5000);

        CloudFile copy = this.share.getRootDirectoryReference().getFileReference("copy");
        String sasToken = copy.generateSharedAccessSignature(policy, null);
        CloudFile copySas = new CloudFile(new URI(copy.getUri().toString() + "?" + sasToken));
        
        // Generate account SAS for the source
        // Cannot generate a SAS directly on a snapshot and the SAS for the destination is only for the destination
        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(policy.getSharedAccessExpiryTime());
        final CloudBlobClient sasClient = TestHelper.createCloudBlobClient(accountPolicy, false);

        CloudBlockBlob snapshot = (CloudBlockBlob) source.createSnapshot();
        CloudBlockBlob sasBlob = (CloudBlockBlob) sasClient.getContainerReference(container.getName())
                .getBlobReferenceFromServer(snapshot.getName(), snapshot.getSnapshotID(), null, null, null);
        sasBlob.exists();

        String copyId = copySas.startCopy(BlobTestHelper.defiddler(sasBlob));
        FileTestHelper.waitForCopy(copySas);
        
        copySas.downloadAttributes();
        FileProperties prop1 = copySas.getProperties();
        BlobProperties prop2 = sasBlob.getProperties();

        assertEquals(prop1.getCacheControl(), prop2.getCacheControl());
        assertEquals(prop1.getContentEncoding(), prop2.getContentEncoding());
        assertEquals(prop1.getContentDisposition(),
                prop2.getContentDisposition());
        assertEquals(prop1.getContentLanguage(), prop2.getContentLanguage());
        assertEquals(prop1.getContentMD5(), prop2.getContentMD5());
        assertEquals(prop1.getContentType(), prop2.getContentType());

        assertEquals("value", copySas.getMetadata().get("Test"));
        assertEquals(copyId, copySas.getCopyState().getCopyId());

        snapshot.delete();
        source.delete();
        copySas.delete();
        container.delete();
    }

    private final static SharedAccessFilePolicy createSharedAccessPolicy(EnumSet<SharedAccessFilePermissions> sap,
            int expireTimeInSeconds) {

        Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        calendar.setTime(new Date());
        calendar.add(Calendar.SECOND, expireTimeInSeconds);
        SharedAccessFilePolicy policy = new SharedAccessFilePolicy();
        policy.setPermissions(sap);
        policy.setSharedAccessExpiryTime(calendar.getTime());
        return policy;
    }

    @SuppressWarnings("unused")
    private static void testAccess(
            String sasToken, EnumSet<SharedAccessFilePermissions> permissions, CloudFileShare share, CloudFile file)
            throws StorageException, URISyntaxException, IOException {
        
        StorageCredentials credentials = new StorageCredentialsSharedAccessSignature(sasToken);
        if (share != null) {
            share = new CloudFileShare(credentials.transformUri(share.getUri()));
            file = share.getRootDirectoryReference().getFileReference(file.getName());
        }
        else {
            file = new CloudFile(credentials.transformUri(file.getUri()));
        }

        if (share != null) {
            if (permissions.contains(SharedAccessFilePermissions.LIST)) {
                for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories());
            }
            else {
                try {
                    for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories());
                    fail();
                }
                catch (NoSuchElementException ex) {
                    assertEquals(
                            HttpURLConnection.HTTP_FORBIDDEN, ((StorageException) ex.getCause()).getHttpStatusCode());
                }
            }
        }

        if (permissions.contains(SharedAccessFilePermissions.READ)) {
            file.downloadAttributes();
        }
        else {
            try {
                file.downloadAttributes();
                fail();
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            }
        }

        if (share != null) {
            if (permissions.contains(SharedAccessFilePermissions.LIST)) {
                for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories());
            }
            else {
                try {
                    for (ListFileItem listedFile : share.getRootDirectoryReference().listFilesAndDirectories());
                    fail();
                }
                catch (NoSuchElementException ex) {
                    assertEquals(
                            HttpURLConnection.HTTP_FORBIDDEN, ((StorageException) ex.getCause()).getHttpStatusCode());
                }
            }
        }

        if (permissions.contains(SharedAccessFilePermissions.READ)) {
            file.downloadAttributes();
        }
        else {
            try {
                file.downloadAttributes();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            }
        }

        if (share != null) {
            final StorageUri uri = PathUtility.appendPathToUri(
                share.getStorageUri(), FileTestHelper.generateRandomFileName());
            final CloudFile upFile = new CloudFile(credentials.transformUri(uri));
            
            if(permissions.contains(SharedAccessFilePermissions.WRITE)) {
                upFile.upload(FileTestHelper.getRandomDataStream(512), 512, null, null, null);
            }
            else {
                if (permissions.contains(SharedAccessFilePermissions.CREATE)) {
                    upFile.create(0);
                }
                
                try {
                    upFile.upload(FileTestHelper.getRandomDataStream(512), 512, null, null, null);
                    fail();
                }
                catch (StorageException ex) {
                    assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
                }
            }
        }

        if (permissions.contains(SharedAccessFilePermissions.WRITE) && 
                permissions.contains(SharedAccessFilePermissions.READ)) {
            file.downloadAttributes();
            file.uploadMetadata();
        }
        else {
            try {
                file.downloadAttributes();
                file.uploadMetadata();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            }
        }

        if (permissions.contains(SharedAccessFilePermissions.DELETE)) {
            file.delete();
        }
        else {
            try {
                file.delete();
                Assert.fail();
            }
            catch (StorageException ex) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, ex.getHttpStatusCode());
            }
        }
    }

    private EnumSet<SharedAccessFilePermissions> getPermissions(int bits) {
        EnumSet<SharedAccessFilePermissions> permissionSet = EnumSet.noneOf(SharedAccessFilePermissions.class);
        if ((bits & 0x1) == 0x1) {
            permissionSet.add(SharedAccessFilePermissions.READ);
        }

        if ((bits & 0x2) == 0x2) {
            permissionSet.add(SharedAccessFilePermissions.CREATE);
        }

        if ((bits & 0x4) == 0x4) {
            permissionSet.add(SharedAccessFilePermissions.WRITE);
        }

        if ((bits & 0x8) == 0x8) {
            permissionSet.add(SharedAccessFilePermissions.DELETE);
        }

        if ((bits & 0x10) == 0x10) {
            permissionSet.add(SharedAccessFilePermissions.LIST);
        }

        return permissionSet;
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class CloudQueueClientGB18030Test {

    // GB18030CharSet is "Ã¥â€¢Å Ã©Â½â€žÃ¤Â¸â€šÃ§â€¹â€ºÃ§â€¹Å“Ã¯Â§Â±Ã¯Â¤Â¬Ã¯Â§Â±Ã¯Â¨Å’Ã¯Â¨Â©Ã‹Å Ã¢â€“â€¡Ã¢â€“Ë†Ã£â‚¬Å¾Ã£â‚¬Â¡Ã¯Â¿Â¤Ã¢â€žÂ¡Ã£Ë†Â±Ã¢â‚¬ï¿½Ã£Æ’Â¼Ã¯Â¹Â¡Ã¯Â¹Â¢Ã¯Â¹Â«Ã£â‚¬ï¿½Ã£â‚¬â€œÃ¢â€¦Â°Ã¢â€¦Â¹Ã¢â€™Ë†Ã¢â€šÂ¬Ã£Ë†Â Ã£Ë†Â©Ã¢â€¦Â Ã¢â€¦Â«Ã¯Â¼ï¿½Ã¯Â¿Â£Ã£ï¿½ï¿½Ã£â€šâ€œÃ£â€šÂ¡Ã£Æ’Â¶ÃŽâ€˜Ã¯Â¸Â´Ã�ï¿½Ã�Â¯Ã�Â°Ã‘ï¿½Ã„ï¿½Ã‰Â¡Ã£â€žâ€¦Ã£â€žÂ©Ã¢â€�â‚¬Ã¢â€¢â€¹Ã¯Â¸ÂµÃ¯Â¹â€žÃ¯Â¸Â»Ã¯Â¸Â±Ã¯Â¸Â³Ã¯Â¸Â´Ã¢â€¦Â°Ã¢â€¦Â¹Ã‰â€˜Ã®Å¸â€¡Ã‰Â¡Ã£â‚¬â€¡Ã£â‚¬Â¾Ã¢Â¿Â»Ã¢Âºï¿½Ã®Â¡Æ’Ã¤Å“Â£Ã®Â¡Â¤Ã¢â€šÂ¬Ã£ï¿½â‚¬Ã£â€™Â£Ã£â€¢Â´Ã£â€¢ÂµÃ£â„¢â€°Ã£â„¢Å Ã¤ÂµÂ¯Ã¤ÂµÂ°Ã¤Â¶Â´Ã¤Â¶Âµ".
    private static final String GB18030CharSet = new String(new char[] { 0x554A, 0x9F44, 0x4E02, 0x72DB, 0x72DC,
            0xF9F1, 0xF92C, 0xF9F1, 0xFA0C, 0xFA29, 0x02CA, 0x2587, 0x2588, 0x301E, 0x3021, 0xFFE4, 0x2121, 0x3231,
            0x2010, 0x30FC, 0xFE61, 0xFE62, 0xFE6B, 0x3001, 0x3013, 0x2170, 0x2179, 0x2488, 0x20AC, 0x3220, 0x3229,
            0x2160, 0x216B, 0xFF01, 0xFFE3, 0x3041, 0x3093, 0x30A1, 0x30F6, 0x0391, 0xFE34, 0x0410, 0x042F, 0x0430,
            0x044F, 0x0101, 0x0261, 0x3105, 0x3129, 0x2500, 0x254B, 0xFE35, 0xFE44, 0xFE3B, 0xFE31, 0xFE33, 0xFE34,
            0x2170, 0x2179, 0x0251, 0xE7C7, 0x0261, 0x3007, 0x303E, 0x2FFB, 0x2E81, 0xE843, 0x4723, 0xE864, 0x20AC,
            0x3400, 0x34A3, 0x3574, 0x3575, 0x3649, 0x364A, 0x4D6F, 0x4D70, 0x4DB4, 0x4DB5 });

    private CloudQueue queue;

    @Before
    public void queueClientGBTestMethodSetUp() throws URISyntaxException, StorageException {
        this.queue = QueueTestHelper.getRandomQueueReference();
        this.queue.createIfNotExists();
    }

    @After
    public void queueClientGBTestMethodTearDown() throws StorageException {
        this.queue.deleteIfExists();
    }

    @Test
    public void testGB18030TestForSingleMessage() throws  StorageException {
        String messageContent = GB18030CharSet;
        CloudQueueMessage cqm = new CloudQueueMessage(messageContent);
        this.queue.addMessage(cqm);

        CloudQueueMessage messageFromPeekMessage = this.queue.peekMessage();
        assertEquals(messageContent, messageFromPeekMessage.getMessageContentAsString());

        CloudQueueMessage messageFromRetrieveMessage = this.queue.retrieveMessage();
        assertEquals(messageContent, messageFromRetrieveMessage.getMessageContentAsString());

        String messageContentUpdated = messageContent + " updated";
        messageFromRetrieveMessage.setMessageContent(messageContentUpdated);
        this.queue.updateMessage(messageFromRetrieveMessage, 0);

        messageFromPeekMessage = this.queue.peekMessage(null, null);
        assertEquals(messageContent, messageFromPeekMessage.getMessageContentAsString());

        messageContentUpdated = messageContent + " updated again";
        messageFromRetrieveMessage.setMessageContent(messageContentUpdated);
        this.queue.updateMessage(messageFromRetrieveMessage, 0,
                EnumSet.of(MessageUpdateFields.VISIBILITY, MessageUpdateFields.CONTENT), null, null);

        messageFromRetrieveMessage = this.queue.retrieveMessage(5, null, null);
        assertEquals(messageContentUpdated, messageFromRetrieveMessage.getMessageContentAsString());

        this.queue.deleteMessage(messageFromRetrieveMessage);
    }

    @Test
    public void testGB18030TestForMultipleMessages() throws  StorageException {
        int messageLength = 2;
        String[] messageContents = new String[messageLength];
        for (int i = 0; i < messageLength; i++) {
            messageContents[i] = GB18030CharSet + i;
            this.queue.addMessage(new CloudQueueMessage(messageContents[i]), 600, 0, null, null);
        }

        Iterable<CloudQueueMessage> messagesFromPeekMessages = this.queue.peekMessages(messageLength);
        int count = 0;
        for (CloudQueueMessage message : messagesFromPeekMessages) {
            assertEquals(messageContents[count], message.getMessageContentAsString());
            count++;
        }

        Iterable<CloudQueueMessage> messagesFromRetrieveMessages = this.queue.retrieveMessages(messageLength);
        count = 0;
        for (CloudQueueMessage message : messagesFromRetrieveMessages) {
            assertEquals(messageContents[count], message.getMessageContentAsString());
            message.setMessageContent(message.getMessageContentAsString() + " updated");
            this.queue.updateMessage(message, 0,
                    EnumSet.of(MessageUpdateFields.VISIBILITY, MessageUpdateFields.CONTENT), null, null);
            count++;
        }

        messagesFromPeekMessages = this.queue.peekMessages(messageLength, null, null);
        count = 0;
        for (CloudQueueMessage message : messagesFromPeekMessages) {
            assertEquals(messageContents[count] + " updated", message.getMessageContentAsString());
            count++;
        }

        messagesFromRetrieveMessages = this.queue.retrieveMessages(messageLength, 5, null, null);
        count = 0;
        for (CloudQueueMessage message : messagesFromRetrieveMessages) {
            assertEquals(messageContents[count] + " updated", message.getMessageContentAsString());
            this.queue.deleteMessage(message, null, null);
            count++;
        }
    }
} @Category({ CloudTests.class })
public class CloudQueueTests {

    private CloudQueue queue;

    @Before
    public void queueTestMethodSetUp() throws URISyntaxException, StorageException {
        this.queue = QueueTestHelper.getRandomQueueReference();
        this.queue.createIfNotExists();
    }

    @After
    public void queueTestMethodTearDown() throws StorageException {
        this.queue.deleteIfExists();
    }

    /**
     * Tests queue name validation.
     */
    @Test
    public void testCloudQueueNameValidation()
    {
        NameValidator.validateQueueName("alpha");
        NameValidator.validateQueueName("4lphanum3r1c");
        NameValidator.validateQueueName("middle-dash");

        invalidQueueTestHelper(null, "Null not allowed.", "Invalid queue name. The name may not be null, empty, or whitespace only.");
        invalidQueueTestHelper("$root", "Alphanumeric or dashes only.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("double--dash", "No double dash.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("CapsLock", "Lowercase only.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("illegal$char", "Alphanumeric or dashes only.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("illegal!char", "Alphanumeric or dashes only.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("white space", "Alphanumeric or dashes only.", "Invalid queue name. Check MSDN for more information about valid naming.");
        invalidQueueTestHelper("2c", "Between 3 and 63 characters.", "Invalid queue name length. The name must be between 3 and 63 characters long.");
        invalidQueueTestHelper(new String(new char[64]).replace("\0", "n"), "Between 3 and 63 characters.", "Invalid queue name length. The name must be between 3 and 63 characters long.");
    }

    private void invalidQueueTestHelper(String queueName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateQueueName(queueName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    /**
     * Get permissions from string
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueuePermissionsFromString() {
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        Date start = cal.getTime();
        cal.add(Calendar.MINUTE, 30);
        Date expiry = cal.getTime();

        SharedAccessQueuePolicy policy = new SharedAccessQueuePolicy();
        policy.setSharedAccessStartTime(start);
        policy.setSharedAccessExpiryTime(expiry);

        policy.setPermissionsFromString("raup");
        assertEquals(EnumSet.of(SharedAccessQueuePermissions.READ, SharedAccessQueuePermissions.ADD,
                SharedAccessQueuePermissions.UPDATE, SharedAccessQueuePermissions.PROCESSMESSAGES),
                policy.getPermissions());

        policy.setPermissionsFromString("rap");
        assertEquals(EnumSet.of(SharedAccessQueuePermissions.READ, SharedAccessQueuePermissions.ADD,
                SharedAccessQueuePermissions.PROCESSMESSAGES), policy.getPermissions());

        policy.setPermissionsFromString("ar");
        assertEquals(EnumSet.of(SharedAccessQueuePermissions.READ, SharedAccessQueuePermissions.ADD),
                policy.getPermissions());

        policy.setPermissionsFromString("u");
        assertEquals(EnumSet.of(SharedAccessQueuePermissions.UPDATE), policy.getPermissions());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testQueueGetSetPermissionTest() throws StorageException, InterruptedException {
        QueuePermissions expectedPermissions;
        QueuePermissions testPermissions;

        // Test new permissions.
        expectedPermissions = new QueuePermissions();
        testPermissions = this.queue.downloadPermissions();
        assertQueuePermissionsEqual(expectedPermissions, testPermissions);

        // Test setting empty permissions.
        this.queue.uploadPermissions(expectedPermissions);
        Thread.sleep(30000);
        testPermissions = this.queue.downloadPermissions();
        assertQueuePermissionsEqual(expectedPermissions, testPermissions);

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

        policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ,
                SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD,
                SharedAccessQueuePermissions.UPDATE));
        expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1);

        this.queue.uploadPermissions(expectedPermissions);
        Thread.sleep(30000);
        testPermissions = this.queue.downloadPermissions();
        assertQueuePermissionsEqual(expectedPermissions, testPermissions);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testQueueSAS() throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException {
        this.queue.addMessage(new CloudQueueMessage("sas queue test"));
        QueuePermissions expectedPermissions;

        expectedPermissions = new QueuePermissions();
        // Add a policy, check setting and getting.
        SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy();
        Calendar now = GregorianCalendar.getInstance();
        now.add(Calendar.MINUTE, -15);
        policy1.setSharedAccessStartTime(now.getTime());
        now.add(Calendar.MINUTE, 30);
        policy1.setSharedAccessExpiryTime(now.getTime());
        String identifier = UUID.randomUUID().toString();

        policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ,
                SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD,
                SharedAccessQueuePermissions.UPDATE));
        expectedPermissions.getSharedAccessPolicies().put(identifier, policy1);

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

        CloudQueue identifierSasQueue = new CloudQueue(PathUtility.addToQuery(this.queue.getUri(),
                this.queue.generateSharedAccessSignature(null, identifier)));

        identifierSasQueue.downloadAttributes();
        identifierSasQueue.exists();

        identifierSasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage message1 = identifierSasQueue.retrieveMessage();
        identifierSasQueue.deleteMessage(message1);

        CloudQueue policySasQueue = new CloudQueue(PathUtility.addToQuery(this.queue.getUri(),
                this.queue.generateSharedAccessSignature(policy1, null)));
        policySasQueue.exists();
        policySasQueue.downloadAttributes();

        policySasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage message2 = policySasQueue.retrieveMessage();
        policySasQueue.deleteMessage(message2);

        // do not give the client and check that the new queue's client has the correct perms
        CloudQueue queueFromUri = new CloudQueue(PathUtility.addToQuery(this.queue.getStorageUri(),
                this.queue.generateSharedAccessSignature(null, "readperm")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), queueFromUri.getServiceClient()
                .getCredentials().getClass().toString());

        // pass in a client which will have different permissions and check the sas permissions are used
        // and that the properties set in the old service client are passed to the new client
        CloudQueueClient queueClient = policySasQueue.getServiceClient();

        // set some arbitrary settings to make sure they are passed on
        queueClient.getDefaultRequestOptions().setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        queueClient.getDefaultRequestOptions().setTimeoutIntervalInMs(1000);
        queueClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry());

        queueFromUri = new CloudQueue(PathUtility.addToQuery(this.queue.getStorageUri(),
                this.queue.generateSharedAccessSignature(null, "readperm")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), queueFromUri.getServiceClient()
                .getCredentials().getClass().toString());
    }

    static void assertQueuePermissionsEqual(QueuePermissions expected, QueuePermissions actual) {
        HashMap<String, SharedAccessQueuePolicy> expectedPolicies = expected.getSharedAccessPolicies();
        HashMap<String, SharedAccessQueuePolicy> actualPolicies = actual.getSharedAccessPolicies();
        assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size());
        for (String name : expectedPolicies.keySet()) {
            assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name));
            SharedAccessQueuePolicy expectedPolicy = expectedPolicies.get(name);
            SharedAccessQueuePolicy 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());

        }

    }

    @Test
    public void testQueueClientConstructor() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();

        CloudQueue queue1 = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue1.getName());
        assertTrue(queue1.getUri().toString().endsWith(queueName));
        assertEquals(qClient, queue1.getServiceClient());

        CloudQueue queue2 = new CloudQueue(new URI(QueueTestHelper.appendQueueName(qClient.getEndpoint(), queueName)),
                qClient.getCredentials());

        assertEquals(queueName, queue2.getName());
        assertEquals(qClient.getCredentials(), queue2.getServiceClient().getCredentials());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testGetMetadata() throws StorageException {
        HashMap<String, String> metadata = new HashMap<String, String>();
        metadata.put("ExistingMetadata", "ExistingMetadataValue");
        this.queue.setMetadata(metadata);
        this.queue.uploadMetadata();
        this.queue.downloadAttributes();
        assertEquals(this.queue.getMetadata().get("ExistingMetadata"), "ExistingMetadataValue");
        assertTrue(this.queue.getMetadata().containsKey("ExistingMetadata"));

        HashMap<String, String> empytMetadata = null;
        this.queue.setMetadata(empytMetadata);
        this.queue.uploadMetadata();
        this.queue.downloadAttributes();
        assertTrue(this.queue.getMetadata().size() == 0);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadMetadata() throws URISyntaxException, StorageException {
        CloudQueue queueForGet =  this.queue.getServiceClient().getQueueReference(this.queue.getName());

        HashMap<String, String> metadata1 = new HashMap<String, String>();
        metadata1.put("ExistingMetadata1", "ExistingMetadataValue1");
        this.queue.setMetadata(metadata1);

        queueForGet.downloadAttributes();
        assertFalse(queueForGet.getMetadata().containsKey("ExistingMetadata1"));

        this.queue.uploadMetadata();
        queueForGet.downloadAttributes();
        assertTrue(queueForGet.getMetadata().containsKey("ExistingMetadata1"));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadMetadataNullInput() throws URISyntaxException, StorageException {
        CloudQueue queueForGet =  this.queue.getServiceClient().getQueueReference(this.queue.getName());

        HashMap<String, String> metadata1 = new HashMap<String, String>();
        String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", "");
        metadata1.put(key, "ExistingMetadataValue1");
        this.queue.setMetadata(metadata1);

        queueForGet.downloadAttributes();
        assertFalse(queueForGet.getMetadata().containsKey(key));

        this.queue.uploadMetadata();
        queueForGet.downloadAttributes();
        assertTrue(queueForGet.getMetadata().containsKey(key));

        this.queue.setMetadata(null);
        this.queue.uploadMetadata();
        queueForGet.downloadAttributes();
        assertTrue(queueForGet.getMetadata().size() == 0);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadMetadataClearExisting() throws URISyntaxException, StorageException {
        CloudQueue queueForGet =  this.queue.getServiceClient().getQueueReference(this.queue.getName());

        HashMap<String, String> metadata1 = new HashMap<String, String>();
        String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", "");
        metadata1.put(key, "ExistingMetadataValue1");
        this.queue.setMetadata(metadata1);

        queueForGet.downloadAttributes();
        assertFalse(queueForGet.getMetadata().containsKey(key));

        HashMap<String, String> metadata2 = new HashMap<String, String>();
        this.queue.setMetadata(metadata2);
        this.queue.uploadMetadata();
        queueForGet.downloadAttributes();
        assertTrue(queueForGet.getMetadata().size() == 0);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUploadMetadataNotFound() throws URISyntaxException, StorageException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.uploadMetadata();
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreate() throws URISyntaxException, StorageException {
        CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        OperationContext createQueueContext = new OperationContext();
        try {
            queue.create(null, createQueueContext);
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            try {
                HashMap<String, String> metadata1 = new HashMap<String, String>();
                metadata1.put("ExistingMetadata1", "ExistingMetadataValue1");
                queue.setMetadata(metadata1);
                queue.create();
                fail();
            }
            catch (StorageException e) {
                assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT);

            }

            queue.downloadAttributes();
            OperationContext createQueueContext2 = new OperationContext();
            queue.create(null, createQueueContext2);
            assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }
        finally {
            queue.delete();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateAlreadyExists() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {
            OperationContext createQueueContext1 = new OperationContext();
            queue.create(null, createQueueContext1);
            assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            OperationContext createQueueContext2 = new OperationContext();
            queue.create(null, createQueueContext2);
            assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateAfterDelete() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {
            OperationContext createQueueContext1 = new OperationContext();
            assertTrue(queue.createIfNotExists(null, createQueueContext1));
            assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            assertTrue(queue.deleteIfExists());
            try {
                queue.create();
                fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State");
            }
            catch (StorageException ex) {
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(),
                        HttpURLConnection.HTTP_CONFLICT);
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getExtendedErrorInformation()
                        .getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED);
            }
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateIfNotExists() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {
            OperationContext createQueueContext = new OperationContext();
            assertTrue(queue.createIfNotExists(null, createQueueContext));
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            assertFalse(queue.createIfNotExists());
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateIfNotExistsAfterCreate() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {
            OperationContext createQueueContext1 = new OperationContext();
            assertTrue(queue.createIfNotExists(null, createQueueContext1));

            OperationContext createQueueContext2 = new OperationContext();
            assertFalse(queue.createIfNotExists(null, createQueueContext2));
        }
        finally {
            queue.deleteIfExists();
        }
    }


    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateIfNotExistsAfterDelete() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {

            OperationContext createQueueContext1 = new OperationContext();
            assertTrue(queue.createIfNotExists(null, createQueueContext1));
            assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            assertTrue(queue.deleteIfExists());
            try {
                queue.createIfNotExists();
                fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State");
            }
            catch (StorageException ex) {
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(),
                        HttpURLConnection.HTTP_CONFLICT);
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getExtendedErrorInformation()
                        .getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED);
            }
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueDelete() throws URISyntaxException, StorageException {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        CloudQueue queue = qClient.getQueueReference(queueName);
        assertEquals(queueName, queue.getName());

        try {
            OperationContext createQueueContext = new OperationContext();
            queue.create(null, createQueueContext);
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            OperationContext deleteQueueContext = new OperationContext();
            queue.delete(null, deleteQueueContext);
            assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

            try {
                queue.downloadAttributes();
                fail();
            }
            catch (StorageException ex) {
                assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
            }
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteQueueIfExists() throws URISyntaxException, StorageException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        assertFalse(queue.deleteIfExists());

        try {
            final OperationContext createQueueContext = new OperationContext();
            queue.create(null, createQueueContext);
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            assertTrue(queue.deleteIfExists());
            assertFalse(queue.deleteIfExists());

            try {
                queue.create();
                fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State");
            }
            catch (StorageException ex) {
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(),
                        HttpURLConnection.HTTP_CONFLICT);
                assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getExtendedErrorInformation()
                        .getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED);
            }
        }
        finally {
            queue.delete();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudQueueDeleteIfExistsErrorCode() throws StorageException, URISyntaxException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.delete();
            fail("Queue should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.QUEUE_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 {
                        queue.delete();
                        assertFalse(queue.exists());
                    }
                    catch (StorageException e) {
                        fail("Delete should succeed.");
                    }
                }
            }
        });

        try {
            queue.create();

            // Queue deletes succeed before garbage collection occurs.
            assertTrue(queue.deleteIfExists(null, ctx));
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteNonExistingQueue() throws URISyntaxException, StorageException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        final OperationContext existQueueContext1 = new OperationContext();
        assertTrue(!queue.exists(null, existQueueContext1));
        assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);

        try {
            queue.delete();
            fail("Queue delete no exsiting queue. ");
        }
        catch (StorageException ex) {
            assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueExist() throws URISyntaxException, StorageException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        final OperationContext existQueueContext1 = new OperationContext();
        assertTrue(!queue.exists(null, existQueueContext1));
        assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);

        try {
            final OperationContext createQueueContext = new OperationContext();
            queue.create(null, createQueueContext);
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);

            final OperationContext existQueueContext2 = new OperationContext();
            assertTrue(queue.exists(null, existQueueContext2));
            assertEquals(existQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_OK);
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageVerifyPopReceipt() throws StorageException
    {
        CloudQueueMessage message1 = new CloudQueueMessage("firstmessagetest1");
        message1.setNextVisibleTime(null);
        this.queue.addMessage(message1);

        VerifyAddMessageResult(message1, "firstmessagetest1");
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteMessageWithAddMessagePopReceipt() throws StorageException
    {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        message1.setNextVisibleTime(null);
        this.queue.addMessage(message1);

        VerifyAddMessageResult(message1, "messagetest1");

        queue.deleteMessage(message1);

        assertNull(queue.retrieveMessage());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testClearMessages() throws StorageException {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);
        VerifyAddMessageResult(message1, "messagetest1");

        CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);
        VerifyAddMessageResult(message2, "messagetest2");

        int count = 0;
        for (CloudQueueMessage m : this.queue.peekMessages(32)) {
            assertNotNull(m);
            count++;
        }

        assertTrue(count == 2);

        OperationContext oc = new OperationContext();
        this.queue.clear(null, oc);
        assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        count = 0;
        for (CloudQueueMessage m : this.queue.peekMessages(32)) {
            assertNotNull(m);
            count++;
        }

        assertTrue(count == 0);
    }

    public void testClearMessagesEmptyQueue() throws StorageException {
        this.queue.clear();
        this.queue.delete();
    }

    public void testClearMessagesNotFound() throws  StorageException, URISyntaxException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.clear();
            fail();
        }
        catch (StorageException ex) {
            assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessage() throws  StorageException {
        String msgContent = UUID.randomUUID().toString();
        final CloudQueueMessage message = new CloudQueueMessage(msgContent);
        this.queue.addMessage(message);
        VerifyAddMessageResult(message, msgContent);
        CloudQueueMessage msgFromRetrieve1 = this.queue.retrieveMessage();
        assertEquals(message.getMessageContentAsString(), msgContent);
        assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageUnicode() throws StorageException {
        ArrayList<String> messages = new ArrayList<String>();
        messages.add("Le débat sur l'identité nationale, l'idée du président Nicolas Sarkozy de déchoir des personnes d'origine étrangère de la nationalité française ... certains cas et les récentes mesures prises contre les Roms ont choqué les experts, qui rendront leurs conclusions le 27 août.");
        messages.add("Ваш логин Yahoo! дает доступ к таким мощным инструментам связи, как электронная почта, отправка мгновенных сообщений, функции безопасности, в частности, антивирусные средства и блокировщик всплывающей рекламы, и избранное, например, фото и музыка в сети — все бесплат");
        messages.add("据新华社8月12日电 8月11日晚，舟曲境内再次出现强降雨天气，使特大山洪泥石流灾情雪上加霜。白龙江水在梨坝子村的交汇地带形成一个新的堰塞湖，水位比平时高出3米。甘肃省国土资源厅副厅长张国华当日22时许在新闻发布会上介绍，截至12日21时50分，舟曲堰塞湖堰塞体已消除，溃坝险情已消除，目前针对堰塞湖的主要工作是疏通河道。");
        messages.add("ל כולם\", הדהים יעלון, ויישר קו עם העדות שמסר ראש הממשלה, בנימין נתניהו, לוועדת טירקל. לדבריו, אכן השרים דנו רק בהיבטים התקשורתיים של עצירת המשט: \"בשביעייה לא התקיים דיון על האלטרנטיבות. עסקנו בהיבטים ");
        messages.add("Prozent auf 0,5 Prozent. Im Vergleich zum Vorjahresquartal wuchs die deutsche Wirtschaft von Januar bis März um 2,1 Prozent. Auch das ist eine Korrektur nach oben, ursprünglich waren es hier 1,7 Prozent");
        messages.add("<?xml version=\"1.0\"?>\n<!DOCTYPE PARTS SYSTEM \"parts.dtd\">\n<?xml-stylesheet type=\"text/css\" href=\"xmlpartsstyle.css\"?>\n<PARTS>\n   <TITLE>Computer Parts</TITLE>\n   <PART>\n      <ITEM>Motherboard</ITEM>\n      <MANUFACTURER>ASUS</MANUFACTURER>\n      <MODEL>"
                + "P3B-F</MODEL>\n      <COST> 123.00</COST>\n   </PART>\n   <PART>\n      <ITEM>Video Card</ITEM>\n      <MANUFACTURER>ATI</MANUFACTURER>\n      <MODEL>All-in-Wonder Pro</MODEL>\n      <COST> 160.00</COST>\n   </PART>\n   <PART>\n      <ITEM>Sound Card</ITEM>\n      <MANUFACTURER>"
                + "Creative Labs</MANUFACTURER>\n      <MODEL>Sound Blaster Live</MODEL>\n      <COST> 80.00</COST>\n   </PART>\n   <PART>\n      <ITEM> inch Monitor</ITEM>\n      <MANUFACTURER>LG Electronics</MANUFACTURER>\n      <MODEL> 995E</MODEL>\n      <COST> 290.00</COST>\n   </PART>\n</PARTS>");

        for (int i = 0; i < messages.size(); i++) {
            String msg = messages.get(i);
            this.queue.addMessage(new CloudQueueMessage(msg));
            CloudQueueMessage readBack = this.queue.retrieveMessage();
            assertEquals(msg, readBack.getMessageContentAsString());
            this.queue.deleteMessage(readBack);
        }

        this.queue.setShouldEncodeMessage(false);
        for (int i = 0; i < messages.size(); i++) {
            String msg = messages.get(i);
            this.queue.addMessage(new CloudQueueMessage(msg));
            CloudQueueMessage readBack = this.queue.retrieveMessage();
            assertEquals(msg, readBack.getMessageContentAsString());
            this.queue.deleteMessage(readBack);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageLargeVisibilityDelay() throws  StorageException
    {
        String msgContent = UUID.randomUUID().toString();
        final CloudQueueMessage message = new CloudQueueMessage(msgContent);
        this.queue.addMessage(message, 100, 50, null, null);
        CloudQueueMessage msgFromRetrieve1 = this.queue.retrieveMessage();
        assertNull(msgFromRetrieve1);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteMessageWithDifferentQueueInstance() throws  StorageException, URISyntaxException
    {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final String queueName = QueueTestHelper.generateRandomQueueName();
        final CloudQueue queue1 = qClient.getQueueReference(queueName);
        try {
            queue1.create();

            String msgContent = UUID.randomUUID().toString();
            final CloudQueueMessage message = new CloudQueueMessage(msgContent);
            queue1.addMessage(message);
            CloudQueueMessage msgFromRetrieved = queue1.retrieveMessage();

            final CloudQueue queue2 = qClient.getQueueReference(queueName);
            queue2.deleteMessage(msgFromRetrieved);
        }
        finally {
            queue1.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageToNonExistingQueue() throws  StorageException, URISyntaxException
    {
        final CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final CloudQueue queue = qClient.getQueueReference(QueueTestHelper.generateRandomQueueName());

        String messageContent = "messagetest";
        CloudQueueMessage message1 = new CloudQueueMessage(messageContent);

        try {
            queue.addMessage(message1);
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueUnicodeAndXmlMessageTest() throws  StorageException
    {
        String msgContent = "ÃƒÆ’Ã†â€™Ãƒâ€ Ã¢â‚¬â„¢ÃƒÆ’Ã¢â‚¬Å¡Ãƒâ€šÃ‚Â¥ÃƒÆ’Ã†â€™ÃƒÂ¢Ã¢â€šÂ¬Ã…Â¡ÃƒÆ’Ã¢â‚¬Å¡Ãƒâ€šÃ‚Â¥ÃƒÆ’Ã†â€™ÃƒÂ¢Ã¢â€šÂ¬Ã…Â¡ÃƒÆ’Ã¢â‚¬Å¡Ãƒâ€šÃ‚Â½<?xml version= 1.0  encoding= utf-8  ?>";
        final CloudQueueMessage message = new CloudQueueMessage(msgContent);
        this.queue.addMessage(message);
        CloudQueueMessage msgFromRetrieve1 = this.queue.retrieveMessage();
        assertEquals(message.getMessageContentAsString(), msgContent);
        assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageLargeMessageInput() throws  StorageException
    {
        final Random rand = new Random();

        byte[] content = new byte[64 * 1024];
        rand.nextBytes(content);
        CloudQueueMessage message1 = new CloudQueueMessage(new String(content));

        try {
            this.queue.addMessage(message1);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        this.queue.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testAddMessageWithVisibilityTimeout() throws StorageException, InterruptedException {
        this.queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage m1 = this.queue.retrieveMessage();
        Date d1 = m1.getExpirationTime();
        this.queue.deleteMessage(m1);

        Thread.sleep(2000);

        this.queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage m2 = this.queue.retrieveMessage();
        Date d2 = m2.getExpirationTime();
        this.queue.deleteMessage(m2);
        assertTrue(d1.before(d2));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageNullMessage() throws  StorageException {
        try {
            this.queue.addMessage(null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testAddMessageSpecialVisibilityTimeout() throws  StorageException
    {
        CloudQueueMessage message = new CloudQueueMessage("test");
        this.queue.addMessage(message, 1, 0, null, null);
        this.queue.addMessage(message, 7 * 24 * 60 * 60, 0, null, null);
        this.queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60 - 1, null, null);

        try {
            this.queue.addMessage(message, 0, -1, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.addMessage(message, 7 * 24 * 60 * 60 + 1, 0, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.addMessage(message, 0, 7 * 24 * 60 * 60 + 1, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteMessage() throws  StorageException {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);

        for (CloudQueueMessage message : this.queue.retrieveMessages(32)) {
            OperationContext deleteQueueContext = new OperationContext();
            this.queue.deleteMessage(message, null, deleteQueueContext);
            assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }

        assertTrue(this.queue.retrieveMessage() == null);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueCreateAddingMetadata() throws  StorageException, URISyntaxException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        final HashMap<String, String> metadata = new HashMap<String, String>(5);
        for (int i = 0; i < 5; i++) {
            metadata.put("key" + i, "value" + i);
        }

        queue.setMetadata(metadata);

        final OperationContext createQueueContext = new OperationContext();

        try {
            queue.create(null, createQueueContext);
            assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED);
        }
        finally {
            queue.deleteIfExists();
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDeleteMessageNullMessage() throws  StorageException
    {
        try {
            this.queue.deleteMessage(null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testRetrieveMessage() throws StorageException, InterruptedException {
        this.queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        OperationContext opContext = new OperationContext();
        CloudQueueMessage message1 = this.queue.retrieveMessage(10, null /*QueueRequestOptions*/, opContext);
        Date expirationTime1 = message1.getExpirationTime();
        Date insertionTime1 = message1.getInsertionTime();
        Date nextVisibleTime1 = message1.getNextVisibleTime();

        assertEquals(HttpURLConnection.HTTP_OK, opContext.getLastResult().getStatusCode());

        this.queue.deleteMessage(message1);

        Thread.sleep(2000);

        this.queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage message2 = this.queue.retrieveMessage();
        Date expirationTime2 = message2.getExpirationTime();
        Date insertionTime2 = message2.getInsertionTime();
        Date nextVisibleTime2 = message2.getNextVisibleTime();
        this.queue.deleteMessage(message2);
        assertTrue(expirationTime1.before(expirationTime2));
        assertTrue(insertionTime1.before(insertionTime2));
        assertTrue(nextVisibleTime1.before(nextVisibleTime2));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessageNonExistingQueue() throws  StorageException, URISyntaxException
    {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.retrieveMessage();
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessageInvalidInput() throws  StorageException, URISyntaxException
    {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();

        try {
            queue.retrieveMessage(-1, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            queue.retrieveMessage(7 * 24 * 3600 + 1, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessagesFromEmptyQueue() throws StorageException {
        for (CloudQueueMessage m : this.queue.retrieveMessages(32)) {
            assertTrue(m.getId() != null);
            assertTrue(m.getPopReceipt() == null);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessagesNonFound() throws  StorageException, URISyntaxException
    {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.retrieveMessages(1);
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testDequeueCountIncreases() throws StorageException, InterruptedException {
        this.queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null);
        CloudQueueMessage message1 = this.queue.retrieveMessage(1, null, null);
        assertTrue(message1.getDequeueCount() == 1);

        for (int i = 2; i < 5; i++) {
            Thread.sleep(2000);
            CloudQueueMessage message2 = this.queue.retrieveMessage(1, null, null);
            assertTrue(message2.getDequeueCount() == i);
        }

    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessageSpecialVisibilityTimeout() throws  StorageException
    {
        try {
            this.queue.retrieveMessage(-1, null, null);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessages() throws  StorageException {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);

        for (CloudQueueMessage m : this.queue.retrieveMessages(32)) {
            assertTrue(m.getId() != null);
            assertTrue(m.getPopReceipt() != null);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testRetrieveMessagesInvalidInput() throws  StorageException
    {
        for (int i = 0; i < 33; i++) {
            this.queue.addMessage(new CloudQueueMessage("test" + i));
        }

        this.queue.retrieveMessages(1, 1, null, null);
        this.queue.retrieveMessages(32, 1, null, null);

        try {
            this.queue.retrieveMessages(-1);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.retrieveMessages(0);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.retrieveMessages(33);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessage() throws  StorageException {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        CloudQueueMessage msg = this.queue.peekMessage();
        assertTrue(msg.getId() != null);
        assertTrue(msg.getPopReceipt() == null);

        this.queue.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessages() throws  StorageException {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);

        for (CloudQueueMessage m : this.queue.peekMessages(32)) {
            assertTrue(m.getId() != null);
            assertTrue(m.getPopReceipt() == null);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessagesInvalidInput() throws  StorageException
    {
        for (int i = 0; i < 33; i++) {
            this.queue.addMessage(new CloudQueueMessage("test" + i));
        }

        this.queue.peekMessages(1);
        this.queue.peekMessages(32);

        try {
            this.queue.peekMessages(-1);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.peekMessages(0);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }

        try {
            this.queue.peekMessages(33);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessageNonExistingQueue() throws  StorageException, URISyntaxException
    {
        CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.peekMessage();
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessagesNonFound() throws  StorageException, URISyntaxException {
        CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.peekMessages(1);
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testPeekMessagesFromEmptyQueue() throws StorageException {
        for (CloudQueueMessage m : this.queue.peekMessages(32)) {
            assertTrue(m.getId() != null);
            assertTrue(m.getPopReceipt() == null);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUpdateMessage() throws StorageException {
        this.queue.clear();

        String messageContent = "messagetest";
        CloudQueueMessage message1 = new CloudQueueMessage(messageContent);
        this.queue.addMessage(message1);

        CloudQueueMessage message2 = new CloudQueueMessage(messageContent);
        this.queue.addMessage(message2);

        String newMesage = message1.getMessageContentAsString() + "updated";

        for (CloudQueueMessage message : this.queue.retrieveMessages(32)) {
            OperationContext oc = new OperationContext();
            message.setMessageContent(newMesage);
            this.queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, oc);
            assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
            CloudQueueMessage messageFromGet = this.queue.retrieveMessage();
            assertEquals(messageFromGet.getMessageContentAsString(), messageContent);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class })
    public void testUpdateMessageFullPass() throws  StorageException, InterruptedException {
        CloudQueueMessage message = new CloudQueueMessage("message");
        this.queue.addMessage(message, 20, 0, null, null);
        CloudQueueMessage message1 = this.queue.retrieveMessage();
        String popreceipt1 = message1.getPopReceipt();
        Date NextVisibleTim1 = message1.getNextVisibleTime();
        this.queue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null);
        String popreceipt2 = message1.getPopReceipt();
        Date NextVisibleTim2 = message1.getNextVisibleTime();
        assertTrue(popreceipt2 != popreceipt1);
        assertTrue(NextVisibleTim1.before(NextVisibleTim2));

        Thread.sleep(2000);

        String newMesage = message.getMessageContentAsString() + "updated";
        message.setMessageContent(newMesage);
        OperationContext oc = new OperationContext();
        this.queue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc);
        assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        String popreceipt3 = message1.getPopReceipt();
        Date NextVisibleTim3 = message1.getNextVisibleTime();
        assertTrue(popreceipt3 != popreceipt2);
        assertTrue(NextVisibleTim2.before(NextVisibleTim3));

        assertTrue(this.queue.retrieveMessage() == null);

        this.queue.updateMessage(message1, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null);

        CloudQueueMessage messageFromGet = this.queue.retrieveMessage();
        assertEquals(messageFromGet.getMessageContentAsString(), message1.getMessageContentAsString());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUpdateMessageWithContentChange() throws  StorageException
    {
        CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);

        for (CloudQueueMessage message : this.queue.retrieveMessages(32)) {
            OperationContext oc = new OperationContext();
            message.setMessageContent(message.getMessageContentAsString() + "updated");
            this.queue.updateMessage(message, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc);
            assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUpdateMessageNullMessage() throws  StorageException
    {
        try {
            this.queue.updateMessage(null, 0);
            fail();
        }
        catch (final IllegalArgumentException e) {
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testUpdateMessageInvalidMessage() throws  StorageException
    {
        CloudQueueMessage message = new CloudQueueMessage("test");
        this.queue.addMessage(message, 1, 0, null, null);
        this.queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.CONTENT), null, null);

        this.queue.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testGetApproximateMessageCount() throws StorageException {
        assertTrue(this.queue.getApproximateMessageCount() == 0);
        this.queue.addMessage(new CloudQueueMessage("message1"));
        this.queue.addMessage(new CloudQueueMessage("message2"));
        assertTrue(this.queue.getApproximateMessageCount() == 0);
        this.queue.downloadAttributes();
        assertTrue(this.queue.getApproximateMessageCount() == 2);
        this.queue.delete();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testShouldEncodeMessage() throws  StorageException {
        String msgContent = UUID.randomUUID().toString();
        final CloudQueueMessage message = new CloudQueueMessage(msgContent);
        this.queue.setShouldEncodeMessage(true);
        this.queue.addMessage(message);
        CloudQueueMessage msgFromRetrieve1 = this.queue.retrieveMessage();
        assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent);
        this.queue.deleteMessage(msgFromRetrieve1);

        this.queue.setShouldEncodeMessage(false);
        this.queue.addMessage(message);
        CloudQueueMessage msgFromRetrieve2 = this.queue.retrieveMessage();
        assertEquals(msgFromRetrieve2.getMessageContentAsString(), msgContent);
        this.queue.deleteMessage(msgFromRetrieve2);

        this.queue.setShouldEncodeMessage(true);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueDownloadAttributes() throws  StorageException, URISyntaxException {
        final CloudQueueMessage message1 = new CloudQueueMessage("messagetest1");
        this.queue.addMessage(message1);

        final CloudQueueMessage message2 = new CloudQueueMessage("messagetest2");
        this.queue.addMessage(message2);

        final HashMap<String, String> metadata = new HashMap<String, String>(5);
        int sum = 5;
        for (int i = 0; i < sum; i++) {
            metadata.put("key" + i, "value" + i);
        }

        this.queue.setMetadata(metadata);
        this.queue.uploadMetadata();

        CloudQueueClient qClient = TestHelper.createCloudQueueClient();
        final CloudQueue queue2 = qClient.getQueueReference(this.queue.getName());
        queue2.downloadAttributes();

        assertEquals(sum, queue2.getMetadata().size());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueDownloadAttributesNotFound() throws  StorageException, URISyntaxException {
        final CloudQueue queue = QueueTestHelper.getRandomQueueReference();
        try {
            queue.downloadAttributes();
            fail();
        }
        catch (StorageException e) {
            assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND);

        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testQueueUpdateMetaData() throws  StorageException {
        final HashMap<String, String> metadata = new HashMap<String, String>(5);
        for (int i = 0; i < 5; i++) {
            metadata.put("key" + i, "value" + i);
        }

        this.queue.setMetadata(metadata);
        this.queue.uploadMetadata();
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testSASClientParse() throws StorageException,  InvalidKeyException, URISyntaxException {
        // Add a policy, check setting and getting.
        SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy();
        Calendar now = GregorianCalendar.getInstance();
        now.add(Calendar.MINUTE, -15);
        policy1.setSharedAccessStartTime(now.getTime());
        now.add(Calendar.MINUTE, 30);
        policy1.setSharedAccessExpiryTime(now.getTime());

        policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ,
                SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD,
                SharedAccessQueuePermissions.UPDATE));

        String sasString = this.queue.generateSharedAccessSignature(policy1, null);

        URI queueUri = new URI("http://myaccount.queue.core.windows.net/myqueue");

        CloudQueueClient queueClient1 = new CloudQueueClient(new URI("http://myaccount.queue.core.windows.net/"),
                new StorageCredentialsSharedAccessSignature(sasString));

        CloudQueue queue1 = new CloudQueue(queueUri, queueClient1.getCredentials());
        queue1.getName();

        CloudQueueClient queueClient2 = new CloudQueueClient(new URI("http://myaccount.queue.core.windows.net/"),
                new StorageCredentialsSharedAccessSignature(sasString));
        CloudQueue queue2 = new CloudQueue(queueUri, queueClient2.getCredentials());
        queue2.getName();
    }

    private void VerifyAddMessageResult(CloudQueueMessage originalMessage, String expectedMessageContent)
    {
        assertFalse(Utility.isNullOrEmpty(originalMessage.getId()));
        assertNotNull(originalMessage.getInsertionTime());
        assertNotNull(originalMessage.getExpirationTime());
        assertFalse(Utility.isNullOrEmpty(originalMessage.getPopReceipt()));

        assertTrue(originalMessage.messageContent.equals(expectedMessageContent));
        assertNotNull(originalMessage.getNextVisibleTime());
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class TableBatchOperationTests {

    private CloudTable table;

    @Before
    public void tableBatchOperationTestMethodSetUp() throws URISyntaxException, StorageException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();
    }

    @After
    public void tableBatchOperationTestMethodTearDown() throws StorageException {
        this.table.deleteIfExists();
    }

    @Test
    public void testBatchAddAll() throws StorageException {
        ArrayList<TableOperation> ops = allOpsList();

        TableBatchOperation batch = new TableBatchOperation();
        boolean added = batch.addAll(ops);
        assertTrue(added);

        ArrayList<TableResult> results = this.table.execute(batch, null, null);
        assertEquals(8, results.size());

        Iterator<TableResult> iter = results.iterator();

        for (int i = 0; i < 7; i++) {
            assertEquals(HttpURLConnection.HTTP_NO_CONTENT, iter.next().getHttpStatusCode());
        }

        // test to make sure we can't now add a query with addAll()
        ops.clear();
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        ops.add(TableOperation.retrieve(ref.partitionKey, ref.rowKey, ref.getClass()));
        try {
            batch.addAll(ops);
            fail(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
        catch (Exception e) {
            assertEquals(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH, e.getMessage());
        }

        // test to make sure we can't now add an operation with a different partition key with addAll()
        ops.clear();
        ref.partitionKey = "jxscl_odata_different";
        ops.add(TableOperation.insert(ref));
        try {
            batch.addAll(ops);
            fail(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY);
        }
        catch (Exception e) {
            assertEquals(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY, e.getMessage());
        }
    }

    @Test
    public void testBatchAddAllIndex() throws StorageException {
        ArrayList<TableOperation> ops = allOpsList();

        TableBatchOperation batch = new TableBatchOperation();
        boolean added = batch.addAll(0, ops);
        assertTrue(added);

        ArrayList<TableResult> results = this.table.execute(batch, null, null);
        assertEquals(8, results.size());

        Iterator<TableResult> iter = results.iterator();

        for (int i = 0; i < 7; i++) {
            assertEquals(HttpURLConnection.HTTP_NO_CONTENT, iter.next().getHttpStatusCode());
        }

        // test to make sure we can't now add a query with addAll()
        ops.clear();
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        ops.add(TableOperation.retrieve(ref.partitionKey, ref.rowKey, ref.getClass()));
        try {
            batch.addAll(0, ops);
            fail(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
        catch (Exception e) {
            assertEquals(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH, e.getMessage());
        }

        // test to make sure we can't now add an operation with a different partition key with addAll()
        ops.clear();
        ref.partitionKey = "jxscl_odata_different";
        ops.add(TableOperation.insert(ref));
        try {
            batch.addAll(0, ops);
            fail(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY);
        }
        catch (Exception e) {
            assertEquals(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY, e.getMessage());
        }
    }

    @Test
    public void testBatchAddAllWithRetrieveShouldThrow() throws StorageException {
        ArrayList<TableOperation> ops = allOpsList();

        TableBatchOperation batch = new TableBatchOperation();

        // Insert entity to retrieve
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), null, null);
        ops.add(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(), Class1.class));

        try {
            batch.addAll(ops);
            fail(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH, e.getMessage());
        }
    }

    @Test
    @Category(SecondaryTests.class)
    public void testBatchAddIndex() {
        TableBatchOperation batch = new TableBatchOperation();

        // add a retrieve
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        TableOperation queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(0, queryOp);

        // remove the retrieve
        batch.remove(0);
        assertEquals(0, batch.size());

        // should be able to add an entity with a different partition key
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata_2");
        TableOperation op = TableOperation.insert(baseEntity);
        batch.add(0, op);

        // remove the insert
        batch.remove(0);
        assertEquals(0, batch.size());

        // add a retrieve to the batch
        ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(0, queryOp);
    }

    @Test
    public void testBatchRemoveAll() throws StorageException {
        ArrayList<TableOperation> ops = allOpsList();

        TableBatchOperation batch = new TableBatchOperation();

        batch.addAll(ops);

        assertTrue(batch.removeAll(ops));
        assertEquals(0, batch.size());

        // should be able to add an entity with a different partition key
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata_2");
        batch.insert(baseEntity);
    }

    @Test
    @Category(SecondaryTests.class)
    public void testBatchRemoveRange() throws StorageException {
        ArrayList<TableOperation> ops = allOpsList();

        TableBatchOperation batch = new TableBatchOperation();

        batch.addAll(ops);

        batch.removeRange(0, ops.size());
        assertEquals(0, batch.size());

        // should be able to add an entity with a different partition key
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata_2");
        batch.insert(baseEntity);

        batch.removeRange(0, 1);

        batch.addAll(ops);
        batch.removeRange(0, ops.size() - 1);

        // should be not be able to add an entity with a different partition key
        baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata_2");
        try {
            batch.insert(baseEntity);
            fail(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY);
        }
        catch (IllegalArgumentException e) {
            assertEquals(SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY, e.getMessage());
        }

        batch.removeRange(0, 1);

        // should be able to add a retrieve to the batch
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        TableOperation queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(queryOp);
    }

    @Test
    @Category(SecondaryTests.class)
    public void testBatchRemove() {
        TableBatchOperation batch = new TableBatchOperation();

        // add a retrieve
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        TableOperation queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(queryOp);

        // remove the retrieve
        batch.remove(queryOp);
        assertEquals(0, batch.size());

        // should be able to add an entity with a different partition key
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata_2");
        TableOperation op = TableOperation.insert(baseEntity);
        batch.add(op);

        // remove the insert
        batch.remove(op);
        assertEquals(0, batch.size());

        // should be able to add a retrieve to the batch
        ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(queryOp);
    }

    @Test
    public void testBatchLockToPartitionKey() {
        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.insert(TableTestHelper.generateRandomEntity("jxscl_odata"));
            batch.insert(TableTestHelper.generateRandomEntity("jxscl_odata2"));
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), SR.OPS_IN_BATCH_MUST_HAVE_SAME_PARTITION_KEY);
        }
    }

    @Test
    public void testBatchRetrieveAndOneMoreOperationShouldThrow() {
        Class1 ref2 = TableTestHelper.generateRandomEntity("jxscl_odata");

        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.insert(TableTestHelper.generateRandomEntity("jxscl_odata"));
            batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass());
            fail();
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }

        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass());
            batch.insert(TableTestHelper.generateRandomEntity("jxscl_odata"));
            fail();
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
    }

    @Test
    public void testBatchAddNullShouldThrow() {
        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.add(null);
            fail();
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), String.format(SR.ARGUMENT_NULL_OR_EMPTY, "element"));
        }
    }

    @Test
    public void testBatchRetrieveWithNullResolverShouldThrow() {
        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve("foo", "blah", (EntityResolver<?>) null);
            fail();
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(),
                    String.format(SR.ARGUMENT_NULL_OR_EMPTY, SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER));
        }
    }

    @Test
    public void testEmptyBatchShouldThrow() throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();

        try {
            this.table.execute(batch, null, null);
            fail(SR.EMPTY_BATCH_NOT_ALLOWED);
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), SR.EMPTY_BATCH_NOT_ALLOWED);
        }
    }

    @Test
    public void testBatchMultiQueryShouldThrow() {
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        Class1 ref2 = TableTestHelper.generateRandomEntity("jxscl_odata");

        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
            batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass());
            fail(SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), SR.RETRIEVE_MUST_BE_ONLY_OPERATION_IN_BATCH);
        }
    }

    @Test
    // don't need the category secondary as the request will fail before being sent
    public void testBatchSecondaryWriteShouldThrow() {
        // create batch with an insert
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        TableOperation op = TableOperation.insert(baseEntity);

        TableBatchOperation batch = new TableBatchOperation();
        batch.add(op);

        // should not be able to make a request to secondary as there are writes
        try {
            TableRequestOptions options = new TableRequestOptions();
            options.setLocationMode(LocationMode.SECONDARY_ONLY);
            options.setRetryPolicyFactory(new RetryNoRetry());
            this.table.execute(batch, options, null);
            fail("Should not be able to make a request to secondary as there are writes.");
        }
        catch (StorageException e) {
            assertEquals(IllegalArgumentException.class, e.getCause().getClass());
            assertEquals(SR.PRIMARY_ONLY_COMMAND, e.getCause().getMessage());
        }
    }

    @Test
    @Category(SecondaryTests.class)
    public void testBatchSecondaryNoWrite() throws StorageException {
        // create and insert an entity
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(ref));

        // create a batch and add a query for this entity
        TableBatchOperation batch = new TableBatchOperation();
        TableOperation queryOp = TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());
        batch.add(queryOp);

        // should be able to make a request to secondary as there are no writes
        TableRequestOptions options = new TableRequestOptions();
        options.setLocationMode(LocationMode.SECONDARY_ONLY);
        options.setRetryPolicyFactory(new RetryNoRetry());
        this.table.execute(batch, options, null);
    }

    @Test
    public void testBatchOver100Entities() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();
        try {
            for (int m = 0; m < 101; m++) {
                batch.insert(TableTestHelper.generateRandomEntity("jxscl_odata"));
            }

            this.table.execute(batch, options, null);
            fail("Batch with over 100 entities should fail.");
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage();
            errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1);
            assertTrue(errorAfterSemiColon.startsWith("The batch request operation exceeds the maximum 100 changes per change set."));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.INVALID_INPUT);
        }
    }

    @Test
    public void testBatchInsertEntityOver1MB() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();
        Class1 bigEnt = new Class1();

        bigEnt.setA("foo_A");
        bigEnt.setB("foo_B");
        bigEnt.setC("foo_C");
        // 1mb right here
        bigEnt.setD(new byte[1024 * 1024]);
        bigEnt.setPartitionKey("jxscl_odata");
        bigEnt.setRowKey(UUID.randomUUID().toString());

        batch.insert(bigEnt);

        for (int m = 0; m < 3; m++) {
            Class1 ref = new Class1();
            ref.setA("foo_A");
            ref.setB("foo_B");
            ref.setC("foo_C");
            ref.setPartitionKey("jxscl_odata");
            ref.setRowKey(UUID.randomUUID().toString());
            batch.insert(ref);
        }

        try {
            this.table.execute(batch, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage();
            errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1);
            assertTrue(errorAfterSemiColon.startsWith("The entity is larger than the maximum allowed size (1MB)."));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.ENTITY_TOO_LARGE);
        }
    }

    @Test
    public void testBatchInsertEntityWithPropertyMoreThan255chars() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();
        DynamicTableEntity bigEnt = new DynamicTableEntity();

        String propName = "";
        for (int m = 0; m < 256; m++) {
            propName = propName.concat("a");
        }

        bigEnt.getProperties().put(propName, new EntityProperty("test"));
        bigEnt.setPartitionKey("jxscl_odata");
        bigEnt.setRowKey(UUID.randomUUID().toString());

        batch.insert(bigEnt);

        for (int m = 0; m < 3; m++) {
            Class1 ref = new Class1();
            ref.setA("foo_A");
            ref.setB("foo_B");
            ref.setC("foo_C");
            ref.setPartitionKey("jxscl_odata");
            ref.setRowKey(UUID.randomUUID().toString());
            batch.insert(ref);
        }

        try {
            this.table.execute(batch, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage();
            errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1);
            assertTrue(errorAfterSemiColon.startsWith("The property name exceeds the maximum allowed length (255)."));
            assertEquals(ex.getErrorCode(), "PropertyNameTooLong");
        }
    }

    @Test
    public void testBatchSizeOver4mb() {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();
        byte[] datArr = new byte[1024 * 128];
        Random rand = new Random();
        rand.nextBytes(datArr);

        // Each entity is approx 128kb, meaning ~32 entities will result in a request over 4mb.
        try {
            for (int m = 0; m < 32; m++) {
                Class1 ref = new Class1();

                ref.setA("foo_A");
                ref.setB("foo_B");
                ref.setC("foo_C");
                ref.setD(datArr);
                ref.setPartitionKey("jxscl_odata");
                ref.setRowKey(UUID.randomUUID().toString());
                batch.insert(ref);
            }

            this.table.execute(batch, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(ex.getHttpStatusCode(), HttpURLConnection.HTTP_ENTITY_TOO_LARGE);
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.REQUEST_BODY_TOO_LARGE);
            assertTrue(ex.getMessage().startsWith(
                    "The request body is too large and exceeds the maximum permissible limit."));
        }
    }

    @Test
    public void testBatchDeleteFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();

        // Insert entity to delete
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);

        Class1 updatedEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        updatedEntity.setPartitionKey(baseEntity.getPartitionKey());
        updatedEntity.setRowKey(baseEntity.getRowKey());
        updatedEntity.setEtag(baseEntity.getEtag());
        this.table.execute(TableOperation.replace(updatedEntity), options, null);

        // add delete to fail
        batch.delete(baseEntity);

        try {
            this.table.execute(batch, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.UPDATE_CONDITION_NOT_SATISFIED);
        }
    }

    @Test
    public void testBatchInsertFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        // insert entity
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(ref), options, null);
        try {
            TableBatchOperation batch = new TableBatchOperation();
            batch.insert(ref);
            this.table.execute(batch, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Conflict");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The specified entity already exists"));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.ENTITY_ALREADY_EXISTS);
        }
    }

    @Test
    public void testBatchReplaceFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();

        // Insert entity to merge
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);

        Class1 updatedEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        updatedEntity.setPartitionKey(baseEntity.getPartitionKey());
        updatedEntity.setRowKey(baseEntity.getRowKey());
        updatedEntity.setEtag(baseEntity.getEtag());
        this.table.execute(TableOperation.replace(updatedEntity), options, null);

        // add merge to fail
        addReplaceToBatch(baseEntity, batch);

        try {
            this.table.execute(batch);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.UPDATE_CONDITION_NOT_SATISFIED);
        }
    }

    @Test
    public void testBatchMergeFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableBatchOperation batch = new TableBatchOperation();
        addInsertBatch(batch);

        // Insert entity to merge
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);

        Class1 updatedEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        updatedEntity.setPartitionKey(baseEntity.getPartitionKey());
        updatedEntity.setRowKey(baseEntity.getRowKey());
        updatedEntity.setEtag(baseEntity.getEtag());
        this.table.execute(TableOperation.replace(updatedEntity), options, null);

        // add merge to fail
        addMergeToBatch(baseEntity, batch);

        try {
            this.table.execute(batch, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage();
            errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1);
            assertTrue(errorAfterSemiColon
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.UPDATE_CONDITION_NOT_SATISFIED);
        }
    }

    @Test
    public void testBatchEmptyQuery() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        // insert entity
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");

        TableBatchOperation batch = new TableBatchOperation();
        batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());

        ArrayList<TableResult> results = this.table.execute(batch, options, null);

        assertEquals(results.size(), 1);
        assertNull(results.get(0).getResult());
        assertEquals(results.get(0).getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
    }

    @Test
    public void testBatchWithAllOperations() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchWithAllOperations(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchWithAllOperations(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchWithAllOperations(options);
    }

    private void testBatchWithAllOperations(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        // insert
        addInsertBatch(batch);

        {
            // insert entity to delete
            Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(delRef), options, null);
            batch.delete(delRef);
        }

        {
            // Insert entity to replace
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity), options, null);
            addReplaceToBatch(baseEntity, batch);
        }

        {
            // Insert entity to insert or replace
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity), options, null);
            addInsertOrReplaceToBatch(baseEntity, batch);
        }

        {
            // Insert entity to merge
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity), options, null);
            addMergeToBatch(baseEntity, batch);
        }

        {
            // Insert entity to merge, no pre-existing entity
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity), options, null);
            addInsertOrMergeToBatch(baseEntity, batch);
        }

        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        assertEquals(results.size(), 6);

        Iterator<TableResult> iter = results.iterator();

        // insert
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // replace
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // insert or replace
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // merge
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // insert or merge
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

    }

    @Test
    public void testBatchDelete() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchDelete(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchDelete(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchDelete(options);
    }

    private void testBatchDelete(TableRequestOptions options) throws StorageException {
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");

        // insert entity
        this.table.execute(TableOperation.insert(ref), options, null);

        TableBatchOperation batch = new TableBatchOperation();
        batch.delete(ref);

        ArrayList<TableResult> delResults = this.table.execute(batch, options, null);
        for (TableResult r : delResults) {
            assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }

        try {
            this.table.execute(batch, options, null);
            fail();
        }
        catch (StorageException ex) {
            assertEquals(ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
        }
    }

    @Test
    public void testBatchRetrieve() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchRetrieve(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchRetrieve(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchRetrieve(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        options.setPropertyResolver(new Class1());
        testBatchRetrieve(options);
    }

    private void testBatchRetrieve(TableRequestOptions options) throws StorageException {
        // insert entity
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(ref), options, null);

        TableBatchOperation batch = new TableBatchOperation();
        batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass());

        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        assertEquals(results.size(), 1);

        assertEquals(results.get(0).getHttpStatusCode(), HttpURLConnection.HTTP_OK);
        Class1 retrievedRef = results.get(0).getResultAsType();

        assertEquals(ref.getA(), retrievedRef.getA());
        assertEquals(ref.getB(), retrievedRef.getB());
        assertEquals(ref.getC(), retrievedRef.getC());
        assertTrue(Arrays.equals(ref.getD(), retrievedRef.getD()));

        this.table.execute(TableOperation.delete(ref), options, null);
    }

    @Test
    public void tableBatchRetrieveWithEntityResolver() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        tableBatchRetrieveWithEntityResolver(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        tableBatchRetrieveWithEntityResolver(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        tableBatchRetrieveWithEntityResolver(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        options.setPropertyResolver(new Class1());
        tableBatchRetrieveWithEntityResolver(options);
    }

    private void tableBatchRetrieveWithEntityResolver(TableRequestOptions options) throws StorageException {
        // insert entity
        Class1 randEnt = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(randEnt), options, null);

        TableBatchOperation batch = new TableBatchOperation();
        batch.retrieve(randEnt.getPartitionKey(), randEnt.getRowKey(), new EntityResolver<Class1>() {
            @Override
            public Class1 resolve(String partitionKey, String rowKey, Date timeStamp,
                    HashMap<String, EntityProperty> properties, String etag) {
                assertEquals(properties.size(), 4);
                Class1 ref = new Class1();
                ref.setA(properties.get("A").getValueAsString());
                ref.setB(properties.get("B").getValueAsString());
                ref.setC(properties.get("C").getValueAsString());
                ref.setD(properties.get("D").getValueAsByteArray());
                return ref;
            }
        });

        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        assertEquals(results.size(), 1);

        Class1 ent = (Class1) results.get(0).getResult();

        // Validate results
        assertEquals(ent.getA(), randEnt.getA());
        assertEquals(ent.getB(), randEnt.getB());
        assertEquals(ent.getC(), randEnt.getC());
        assertTrue(Arrays.equals(ent.getD(), randEnt.getD()));
    }

    @Test
    public void testBatchInsert() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchInsert(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchInsert(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchInsert(options);
    }

    private void testBatchInsert(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();

        // Add 3 inserts
        addInsertBatch(batch); // default echo content (true)
        addInsertBatch(batch, true); // set echo content to true
        addInsertBatch(batch, false); // set echo content to false

        // insert entity
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(ref), options, null);
        batch.delete(ref);

        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        assertEquals(results.size(), 4);

        Iterator<TableResult> iter = results.iterator();

        TableResult res = iter.next();
        assertEquals(res.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        res = iter.next();
        assertEquals(res.getHttpStatusCode(), HttpURLConnection.HTTP_CREATED);

        res = iter.next();
        assertEquals(res.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
    }

    @Test
    public void testBatchMerge() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchMerge(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchMerge(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchMerge(options);
    }

    private void testBatchMerge(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        addInsertBatch(batch);

        // insert entity to delete
        Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(delRef));
        batch.delete(delRef);

        // Insert entity to merge
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);
        addMergeToBatch(baseEntity, batch);

        ArrayList<TableResult> results = this.table.execute(batch);
        assertEquals(results.size(), 3);

        Iterator<TableResult> iter = results.iterator();

        // insert
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // merge
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
    }

    @Test
    public void testBatchReplace() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchReplace(options);
    }

    private void testBatchReplace(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        addInsertBatch(batch);

        // insert entity to delete
        Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(delRef), options, null);
        batch.delete(delRef);

        // Insert entity to replace
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);
        addReplaceToBatch(baseEntity, batch);

        ArrayList<TableResult> results = this.table.execute(batch);
        assertEquals(results.size(), 3);

        Iterator<TableResult> iter = results.iterator();

        // insert
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // replace
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
    }

    @Test
    public void testBatchInsertOrMerge() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchInsertOrMerge(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchInsertOrMerge(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchInsertOrMerge(options);
    }

    private void testBatchInsertOrMerge(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        addInsertBatch(batch);

        // insert entity to delete
        Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(delRef), options, null);
        batch.delete(delRef);

        // Insert entity to merge
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);
        addInsertOrMergeToBatch(baseEntity, batch);

        ArrayList<TableResult> results = this.table.execute(batch);
        assertEquals(results.size(), 3);

        Iterator<TableResult> iter = results.iterator();

        // insert
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // merge
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
    }

    @Test
    public void testBatchInsertOrReplace() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testBatchInsertOrReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testBatchInsertOrReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testBatchInsertOrReplace(options);
    }

    private void testBatchInsertOrReplace(TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        addInsertBatch(batch);

        // insert entity to delete
        Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(delRef), options, null);
        batch.delete(delRef);

        // Insert entity to replace
        Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
        this.table.execute(TableOperation.insert(baseEntity), options, null);
        addInsertOrReplaceToBatch(baseEntity, batch);

        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        assertEquals(results.size(), 3);

        Iterator<TableResult> iter = results.iterator();

        // insert
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // delete
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

        // replace
        assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
    }

    @Test
    public void testInsertBatch1() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        insertAndDeleteBatchWithX(1, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        insertAndDeleteBatchWithX(1, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        insertAndDeleteBatchWithX(1, options);
    }

    @Test
    public void testInsertBatch10() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        insertAndDeleteBatchWithX(10, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        insertAndDeleteBatchWithX(10, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        insertAndDeleteBatchWithX(10, options);
    }

    @Test
    public void testInsertBatch100() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        insertAndDeleteBatchWithX(100, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        insertAndDeleteBatchWithX(100, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        insertAndDeleteBatchWithX(100, options);
    }

    @Test
    public void testUpsertBatch1() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        upsertAndDeleteBatchWithX(1, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        upsertAndDeleteBatchWithX(1, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        upsertAndDeleteBatchWithX(1, options);
    }

    @Test
    public void testUpsertBatch10() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        upsertAndDeleteBatchWithX(10, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        upsertAndDeleteBatchWithX(10, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        upsertAndDeleteBatchWithX(10, options);
    }

    @Test
    public void testUpsertBatch100() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        upsertAndDeleteBatchWithX(100, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        upsertAndDeleteBatchWithX(100, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        upsertAndDeleteBatchWithX(100, options);
    }

    private Class1 addInsertBatch(TableBatchOperation batch) {
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        batch.insert(ref);
        return ref;
    }

    private Class1 addInsertBatch(TableBatchOperation batch, boolean echoContent) {
        Class1 ref = TableTestHelper.generateRandomEntity("jxscl_odata");
        batch.insert(ref, echoContent);
        return ref;
    }

    private Class2 addInsertOrMergeToBatch(Class1 baseEntity, TableBatchOperation batch) {
        Class2 secondEntity = createEntityToReplaceOrMerge(baseEntity);
        batch.insertOrMerge(secondEntity);
        return secondEntity;
    }

    private Class2 addInsertOrReplaceToBatch(Class1 baseEntity, TableBatchOperation batch) {
        Class2 secondEntity = createEntityToReplaceOrMerge(baseEntity);
        batch.insertOrReplace(secondEntity);
        return secondEntity;
    }

    private Class2 addMergeToBatch(Class1 baseEntity, TableBatchOperation batch) {
        Class2 secondEntity = createEntityToReplaceOrMerge(baseEntity);
        batch.merge(secondEntity);
        return secondEntity;
    }

    private Class2 addReplaceToBatch(Class1 baseEntity, TableBatchOperation batch) {
        Class2 secondEntity = createEntityToReplaceOrMerge(baseEntity);
        batch.replace(secondEntity);
        return secondEntity;
    }

    private void insertAndDeleteBatchWithX(int x, TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        for (int m = 0; m < x; m++) {
            addInsertBatch(batch);
        }

        TableBatchOperation delBatch = new TableBatchOperation();
        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        for (TableResult r : results) {
            assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
            delBatch.delete((Class1) r.getResult());
        }

        ArrayList<TableResult> delResults = this.table.execute(delBatch, options, null);
        for (TableResult r : delResults) {
            assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }
    }

    private void upsertAndDeleteBatchWithX(int x, TableRequestOptions options) throws StorageException {
        TableBatchOperation batch = new TableBatchOperation();
        for (int m = 0; m < x; m++) {
            addInsertOrMergeToBatch(TableTestHelper.generateRandomEntity("jxscl_odata"), batch);
        }

        TableBatchOperation delBatch = new TableBatchOperation();
        ArrayList<TableResult> results = this.table.execute(batch, options, null);
        for (TableResult r : results) {
            assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
            delBatch.delete((Class2) r.getResult());
        }

        ArrayList<TableResult> delResults = this.table.execute(delBatch, options, null);
        for (TableResult r : delResults) {
            assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        }
    }

    private ArrayList<TableOperation> allOpsList() throws StorageException {
        ArrayList<TableOperation> ops = new ArrayList<TableOperation>();

        // insert
        ops.add(TableOperation.insert(TableTestHelper.generateRandomEntity("jxscl_odata")));

        {
            // Insert entity to delete
            Class1 delRef = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(delRef));
            ops.add(TableOperation.delete(delRef));
        }

        {
            // Insert entity to replace
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity));
            ops.add(TableOperation.replace(createEntityToReplaceOrMerge(baseEntity)));
        }

        {
            // Insert entity to insert or replace
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity));
            ops.add(TableOperation.insertOrReplace(createEntityToReplaceOrMerge(baseEntity)));
        }

        {
            // Insert or replace, no pre-existing entity
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            ops.add(TableOperation.insertOrReplace(createEntityToReplaceOrMerge(baseEntity)));
        }

        {
            // Insert entity to merge
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity));
            ops.add(TableOperation.merge(createEntityToReplaceOrMerge(baseEntity)));
        }

        {
            // Insert entity to insert or merge
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            this.table.execute(TableOperation.insert(baseEntity));
            ops.add(TableOperation.insertOrMerge(baseEntity));
        }

        {
            // Insert or merge, no pre-existing entity
            Class1 baseEntity = TableTestHelper.generateRandomEntity("jxscl_odata");
            ops.add(TableOperation.insertOrMerge(baseEntity));
        }

        return ops;
    }

    private Class2 createEntityToReplaceOrMerge(Class1 baseEntity) {
        Class2 secondEntity = new Class2();
        secondEntity.setL("foo_L");
        secondEntity.setM("foo_M");
        secondEntity.setN("foo_N");
        secondEntity.setO("foo_O");
        secondEntity.setPartitionKey(baseEntity.getPartitionKey());
        secondEntity.setRowKey(baseEntity.getRowKey());
        secondEntity.setEtag(baseEntity.getEtag());
        return secondEntity;
    }
} public class TableClientTests {

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListTablesSegmented() throws URISyntaxException, StorageException {
        TableRequestOptions options = new TableRequestOptions();
        TablePayloadFormat[] formats =
                {TablePayloadFormat.JsonFullMetadata,
                TablePayloadFormat.Json,
                TablePayloadFormat.JsonNoMetadata};

        for (TablePayloadFormat format : formats) {
            options.setTablePayloadFormat(format);
            testListTablesSegmented(options);
        }
    }

    private void testListTablesSegmented(TableRequestOptions options) throws URISyntaxException,
            StorageException {
        final CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableBaseName = TableTestHelper.generateRandomTableName();

        ArrayList<String> tables = new ArrayList<String>();
        for (int m = 0; m < 20; m++) {
            String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m));
            CloudTable table = tClient.getTableReference(name);
            table.create();
            tables.add(name);
        }

        try {
            int currTable = 0;
            ResultSegment<String> segment1 = tClient.listTablesSegmented(tableBaseName, 5, null, options, null);
            assertEquals(5, segment1.getLength());
            for (String s : segment1.getResults()) {
                assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                currTable++;
            }

            ResultSegment<String> segment2 = tClient.listTablesSegmented(tableBaseName, 5,
                    segment1.getContinuationToken(), options, null);
            assertEquals(5, segment2.getLength());
            for (String s : segment2.getResults()) {
                assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                currTable++;
            }

            ResultSegment<String> segment3 = tClient.listTablesSegmented(tableBaseName, 5,
                    segment2.getContinuationToken(), options, null);
            assertEquals(5, segment3.getLength());
            for (String s : segment3.getResults()) {
                assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                currTable++;
            }
        }
        finally {
            for (String s : tables) {
                CloudTable table = tClient.getTableReference(s);
                table.delete();
            }
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListTablesSegmentedMaxResultsValidation()
            throws URISyntaxException, StorageException {
        final CloudTableClient tClient = TableTestHelper.createCloudTableClient();

        // Validation should cause each of these to fail.
        for (int i = 0; i >= -2; i--) {
            try {
                tClient.listTablesSegmented(null, i, null, null, null);
                fail();
            }
            catch (IllegalArgumentException e) {
                assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1)
                        .equals(e.getMessage()));
            }
        }

        assertNotNull(tClient.listTablesSegmented("thereshouldntbeanytableswiththisprefix"));
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListTablesSegmentedNoPrefix() throws URISyntaxException, StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        final CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableBaseName = TableTestHelper.generateRandomTableName();

        ArrayList<String> tables = new ArrayList<String>();
        for (int m = 0; m < 20; m++) {
            String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m));
            CloudTable table = tClient.getTableReference(name);
            table.create();
            tables.add(name);
        }

        try {
            int currTable = 0;
            ResultSegment<String> segment1 = tClient.listTablesSegmented(null, 5, null, options, null);
            assertEquals(5, segment1.getLength());
            for (String s : segment1.getResults()) {
                if (s.startsWith(tableBaseName)) {
                    assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                    currTable++;
                }
            }

            ResultSegment<String> segment2 = tClient.listTablesSegmented(null, 5, segment1.getContinuationToken(),
                    options, null);
            assertEquals(5, segment2.getLength());
            for (String s : segment2.getResults()) {
                if (s.startsWith(tableBaseName)) {
                    assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                    currTable++;
                }
            }

            ResultSegment<String> segment3 = tClient.listTablesSegmented(null, 5, segment2.getContinuationToken(),
                    options, null);
            assertEquals(5, segment3.getLength());
            for (String s : segment3.getResults()) {
                if (s.startsWith(tableBaseName)) {
                    assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                    currTable++;
                }

            }
        }
        finally {
            for (String s : tables) {
                CloudTable table = tClient.getTableReference(s);
                table.delete();
            }
        }
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testListTablesWithIterator() throws URISyntaxException, StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        final CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableBaseName = TableTestHelper.generateRandomTableName();

        ArrayList<String> tables = new ArrayList<String>();
        for (int m = 0; m < 20; m++) {
            String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m));
            CloudTable table = tClient.getTableReference(name);
            table.create();
            tables.add(name);
        }

        try {
            // With prefix
            int currTable = 0;
            Iterable<String> listTables = tClient.listTables(tableBaseName, options, null);
            for (String s : listTables) {
                assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                currTable++;
            }

            assertEquals(20, currTable);
            // Second Iteration
            currTable = 0;
            for (String s : listTables) {
                assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable)));
                currTable++;
            }
            assertEquals(20, currTable);

            // Without prefix
            currTable = 0;
            Iterable<String> listTablesNoPrefix = tClient.listTables();
            for (String s : listTablesNoPrefix) {
                if (s.startsWith(tableBaseName)) {
                    currTable++;
                }
            }

            assertEquals(20, currTable);
            currTable = 0;
            for (String s : listTablesNoPrefix) {
                if (s.startsWith(tableBaseName)) {
                    currTable++;
                }
            }

            assertEquals(20, currTable);
        }
        finally {
            for (String s : tables) {
                CloudTable table = tClient.getTableReference(s);
                table.delete();
            }
        }
    }

    @Test
    @Category({SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testTableSASFromIdentifier() throws StorageException, URISyntaxException, InvalidKeyException,
            InterruptedException {
        CloudTable table = TableTestHelper.getRandomTableReference();
        try {
            table.create();

            TablePermissions expectedPermissions = new TablePermissions();
            String identifier = UUID.randomUUID().toString();
            // Add a policy, check setting and getting.
            SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy();
            Calendar now = GregorianCalendar.getInstance();
            policy1.setSharedAccessStartTime(now.getTime());
            now.add(Calendar.MINUTE, 10);
            policy1.setSharedAccessExpiryTime(now.getTime());

            policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY,
                    SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE));
            expectedPermissions.getSharedAccessPolicies().put(identifier, policy1);

            table.uploadPermissions(expectedPermissions);
            Thread.sleep(30000);

            // Insert 500 entities in Batches to query
            for (int i = 0; i < 5; i++) {
                TableBatchOperation batch = new TableBatchOperation();

                for (int j = 0; j < 100; j++) {
                    Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i));
                    ent.setRowKey(String.format("%06d", j));
                    batch.insert(ent);
                }

                table.execute(batch);
            }

            String sasString = table.generateSharedAccessSignature(null, identifier, null, null, null, null);
            CloudTable tableFromIdentifierSAS = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString));

            {
                Class1 randEnt = TableTestHelper.generateRandomEntity(null);
                TableQuery<Class1> query = TableQuery.from(Class1.class).where(
                        String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050"));

                int count = 0;

                for (Class1 ent : tableFromIdentifierSAS.execute(query)) {
                    assertEquals(ent.getA(), randEnt.getA());
                    assertEquals(ent.getB(), randEnt.getB());
                    assertEquals(ent.getC(), randEnt.getC());
                    assertEquals(ent.getPartitionKey(), "javatables_batch_1");
                    assertEquals(ent.getRowKey(), String.format("%06d", count + 50));
                    count++;
                }

                assertEquals(count, 50);
            }

            {
                Class1 baseEntity = new Class1();
                baseEntity.setA("foo_A");
                baseEntity.setB("foo_B");
                baseEntity.setC("foo_C");
                baseEntity.setD(new byte[] { 0, 1, 2 });
                baseEntity.setPartitionKey("jxscl_odata");
                baseEntity.setRowKey(UUID.randomUUID().toString());

                Class2 secondEntity = new Class2();
                secondEntity.setL("foo_L");
                secondEntity.setM("foo_M");
                secondEntity.setN("foo_N");
                secondEntity.setO("foo_O");
                secondEntity.setPartitionKey(baseEntity.getPartitionKey());
                secondEntity.setRowKey(baseEntity.getRowKey());
                secondEntity.setEtag(baseEntity.getEtag());

                // Insert or merge Entity - ENTITY DOES NOT EXIST NOW.
                TableResult insertResult = tableFromIdentifierSAS.execute(TableOperation.insertOrMerge(baseEntity));

                assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

                // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE
                tableFromIdentifierSAS.execute(TableOperation.insertOrMerge(secondEntity));

                // Retrieve entity
                TableResult queryResult = tableFromIdentifierSAS.execute(TableOperation.retrieve(
                        baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class));

                DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType();

                assertNotNull("Property A", retrievedEntity.getProperties().get("A"));
                assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString());

                assertNotNull("Property B", retrievedEntity.getProperties().get("B"));
                assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString());

                assertNotNull("Property C", retrievedEntity.getProperties().get("C"));
                assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString());

                assertNotNull("Property D", retrievedEntity.getProperties().get("D"));
                assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D")
                        .getValueAsByteArray()));

                // Validate New properties exist
                assertNotNull("Property L", retrievedEntity.getProperties().get("L"));
                assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString());

                assertNotNull("Property M", retrievedEntity.getProperties().get("M"));
                assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString());

                assertNotNull("Property N", retrievedEntity.getProperties().get("N"));
                assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString());

                assertNotNull("Property O", retrievedEntity.getProperties().get("O"));
                assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString());
            }
        }
        finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testTableSASFromPermission() throws StorageException, URISyntaxException, InvalidKeyException {
        CloudTable table = TableTestHelper.getRandomTableReference();
        try {
            table.create();

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

            policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY,
                    SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE));

            // Insert 500 entities in Batches to query
            for (int i = 0; i < 5; i++) {
                TableBatchOperation batch = new TableBatchOperation();

                for (int j = 0; j < 100; j++) {
                    Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i));
                    ent.setRowKey(String.format("%06d", j));
                    batch.insert(ent);
                }

                table.execute(batch);
            }

            String sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", "0",
                    "javatables_batch_9", "9");
            CloudTable tableFromPermission = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString));

            sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", null,
                    "javatables_batch_9", null);
            CloudTable tableFromPermissionJustPks = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString));

            {
                TableBatchOperation batchFromSAS = new TableBatchOperation();

                for (int j = 1000; j < 1010; j++) {
                    Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(0));
                    ent.setRowKey(String.format("%06d", j));
                    batchFromSAS.insert(ent);
                }

                tableFromPermission.execute(batchFromSAS);

                Class1 randEnt = TableTestHelper.generateRandomEntity(null);
                TableQuery<Class1> query = TableQuery.from(Class1.class).where(
                        String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050"));

                int count = 0;

                for (Class1 ent : tableFromPermission.execute(query)) {
                    assertEquals(ent.getA(), randEnt.getA());
                    assertEquals(ent.getB(), randEnt.getB());
                    assertEquals(ent.getC(), randEnt.getC());
                    assertEquals(ent.getPartitionKey(), "javatables_batch_1");
                    assertEquals(ent.getRowKey(), String.format("%06d", count + 50));
                    count++;
                }

                assertEquals(count, 50);

                count = 0;

                for (Class1 ent : tableFromPermissionJustPks.execute(query)) {
                    assertEquals(ent.getA(), randEnt.getA());
                    assertEquals(ent.getB(), randEnt.getB());
                    assertEquals(ent.getC(), randEnt.getC());
                    assertEquals(ent.getPartitionKey(), "javatables_batch_1");
                    assertEquals(ent.getRowKey(), String.format("%06d", count + 50));
                    count++;
                }

                assertEquals(count, 50);
            }

            {
                Class1 baseEntity = new Class1();
                baseEntity.setA("foo_A");
                baseEntity.setB("foo_B");
                baseEntity.setC("foo_C");
                baseEntity.setD(new byte[] { 0, 1, 2 });
                baseEntity.setPartitionKey("javatables_batch_0" + UUID.randomUUID().toString());
                baseEntity.setRowKey("0" + UUID.randomUUID().toString());

                Class2 secondEntity = new Class2();
                secondEntity.setL("foo_L");
                secondEntity.setM("foo_M");
                secondEntity.setN("foo_N");
                secondEntity.setO("foo_O");
                secondEntity.setPartitionKey(baseEntity.getPartitionKey());
                secondEntity.setRowKey(baseEntity.getRowKey());
                secondEntity.setEtag(baseEntity.getEtag());

                TableResult insertResult = tableFromPermission.execute(TableOperation.insertOrMerge(baseEntity));

                // Insert or merge Entity - ENTITY DOES NOT EXIST NOW.

                assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);

                // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE
                tableFromPermission.execute(TableOperation.insertOrMerge(secondEntity));

                // Retrieve entity
                TableResult queryResult = tableFromPermission.execute(TableOperation.retrieve(
                        baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class));

                DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType();

                assertNotNull("Property A", retrievedEntity.getProperties().get("A"));
                assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString());

                assertNotNull("Property B", retrievedEntity.getProperties().get("B"));
                assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString());

                assertNotNull("Property C", retrievedEntity.getProperties().get("C"));
                assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString());

                assertNotNull("Property D", retrievedEntity.getProperties().get("D"));
                assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D")
                        .getValueAsByteArray()));

                // Validate New properties exist
                assertNotNull("Property L", retrievedEntity.getProperties().get("L"));
                assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString());

                assertNotNull("Property M", retrievedEntity.getProperties().get("M"));
                assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString());

                assertNotNull("Property N", retrievedEntity.getProperties().get("N"));
                assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString());

                assertNotNull("Property O", retrievedEntity.getProperties().get("O"));
                assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString());
            }
        }
        finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testTableSASPkRk() throws StorageException, URISyntaxException, InvalidKeyException {
        CloudTable table = TableTestHelper.getRandomTableReference();
        try {
            table.create();

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

            policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY,
                    SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE));

            String sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", "00",
                    "javatables_batch_1", "04");
            StorageCredentialsSharedAccessSignature sasCreds = new StorageCredentialsSharedAccessSignature(sasString);
            CloudTable directTable = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString));
            CloudTable transformedTable = new CloudTable(sasCreds.transformUri(table.getUri()));

            Class1 ent = new Class1("javatables_batch_0", "00");
            directTable.execute(TableOperation.insert(ent));

            ent = new Class1("javatables_batch_0", "01");
            transformedTable.execute(TableOperation.insert(ent));

            ent = new Class1("javatables_batch_2", "01");
            try {
                directTable.execute(TableOperation.insert(ent));
                transformedTable.execute(TableOperation.insert(ent));
            }
            catch (StorageException e) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getHttpStatusCode());
                assertEquals("AuthorizationFailure", e.getExtendedErrorInformation().getErrorCode());
            }

            ent = new Class1("javatables_batch_1", "05");
            try {
                directTable.execute(TableOperation.insert(ent));
                transformedTable.execute(TableOperation.insert(ent));
            }
            catch (StorageException e) {
                assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getHttpStatusCode());
                assertEquals("AuthorizationFailure", e.getExtendedErrorInformation().getErrorCode());
            }

        }
        finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class })
    public void testBackoffTimeOverflow() {
        RetryExponentialRetry exponentialRetry = new RetryExponentialRetry(4000, 100000);
        testBackoffTimeOverflow(exponentialRetry, 100000);

        RetryLinearRetry linearRetry = new RetryLinearRetry(4000, 100000);
        testBackoffTimeOverflow(linearRetry, 100000);
    }

    private void testBackoffTimeOverflow(RetryPolicy retryPolicy, int maxAttempts) {
        Exception e = new Exception();
        OperationContext context = new OperationContext();

        RequestResult requestResult = new RequestResult();
        requestResult.setException(e);
        requestResult.setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR);
        requestResult.setStopDate(new Date());
        requestResult.setTargetLocation(StorageLocation.PRIMARY);

        for (int i = 0; i < maxAttempts; i++) {
            RetryContext retryContext = new RetryContext(i, requestResult, StorageLocation.PRIMARY,
                    LocationMode.PRIMARY_ONLY);
            RetryInfo result = retryPolicy.evaluate(retryContext, context);
            assertNotNull(result);
        }

        RetryContext retryContext = new RetryContext(maxAttempts, requestResult, StorageLocation.PRIMARY,
                LocationMode.PRIMARY_ONLY);
        assertNull(retryPolicy.evaluate(retryContext, context));
    }

    @Test
    @Category( {CloudTests.class })
    public void testGetServiceStats() throws StorageException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        tClient.getDefaultRequestOptions().setLocationMode(LocationMode.SECONDARY_ONLY);
        TableTestHelper.verifyServiceStats(tClient.getServiceStats());
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class TableDateTests {

    private CloudTable table;

    @Before
    public void tableDateTestMethodSetUp() throws URISyntaxException, StorageException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();
    }

    @After
    public void tableDateTestMethodTearDown() throws StorageException {
        this.table.deleteIfExists();
    }

    @Test
    public void testTableQueryRoundTripDate() throws URISyntaxException, StorageException {
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(new Date(1417943712123L));

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(new Date(1421247212800L));
    }

    @Test
    public void testTableQueryRoundTripDateJson() throws URISyntaxException, StorageException {
        // JSON
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, false, TablePayloadFormat.Json);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, false, TablePayloadFormat.Json);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, false, TablePayloadFormat.Json);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, false, TablePayloadFormat.Json);


        // JSON NO METADATA
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, false, TablePayloadFormat.JsonNoMetadata);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, false, TablePayloadFormat.JsonNoMetadata);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, false, TablePayloadFormat.JsonNoMetadata);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, false, TablePayloadFormat.JsonNoMetadata);
    }

    @Test
    public void testTableQueryRoundTripDateJsonCrossVersion()
            throws URISyntaxException, StorageException {
        // JSON
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, false, TablePayloadFormat.Json);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, false, TablePayloadFormat.Json);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, false, TablePayloadFormat.Json);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, false, TablePayloadFormat.Json);

        // JSON NO METADATA
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, false, TablePayloadFormat.JsonNoMetadata);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, false, TablePayloadFormat.JsonNoMetadata);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, false, TablePayloadFormat.JsonNoMetadata);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, false, TablePayloadFormat.JsonNoMetadata);
    }

    @Test
    public void testTableQueryRoundTripDateJsonWithBackwardCompatibility()
            throws URISyntaxException, StorageException {
        // JSON
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, true, TablePayloadFormat.Json);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, true, TablePayloadFormat.Json);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, true, TablePayloadFormat.Json);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, true, TablePayloadFormat.Json);

        // JSON NO METADATA
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, true, TablePayloadFormat.JsonNoMetadata);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, true, TablePayloadFormat.JsonNoMetadata);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, true, TablePayloadFormat.JsonNoMetadata);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, true, TablePayloadFormat.JsonNoMetadata);
    }

    @Test
    public void testTableQueryRoundTripDateJsonCrossVersionWithBackwardCompatibility()
            throws URISyntaxException, StorageException {
        // JSON
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, true, TablePayloadFormat.Json);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, true, TablePayloadFormat.Json);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, true, TablePayloadFormat.Json);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, true, TablePayloadFormat.Json);

        // JSON NO METADATA
        // 2014-12-07T09:15:12.123Z  from Java
        testTableQueryRoundTripDate(
                "2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, true, TablePayloadFormat.JsonNoMetadata);

        // 2015-01-14T14:53:32.800Z  from Java
        testTableQueryRoundTripDate(
                "2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, true, TablePayloadFormat.JsonNoMetadata);

        // 2014-11-29T22:55:21.9876543Z  from .Net
        testTableQueryRoundTripDate(
                "2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, true, TablePayloadFormat.JsonNoMetadata);

        // 2015-02-14T03:11:13.0000229Z  from .Net
        testTableQueryRoundTripDate(
                "2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, true, TablePayloadFormat.JsonNoMetadata);
    }

    private void testTableQueryRoundTripDate(final Date date) throws URISyntaxException, StorageException {
        final String partitionKey = "partitionTest";

        // DateBackwardCompatibility off
        String rowKey = TableTestHelper.generateRandomKeyName();
        DateTestEntity entity = new DateTestEntity(partitionKey, rowKey);
        entity.setDate(date);

        TableOperation put = TableOperation.insertOrReplace(entity);
        this.table.execute(put);

        TableOperation get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class);
        entity = this.table.execute(get).getResultAsType();
        assertEquals(date.getTime(), entity.getDate().getTime());

        // DateBackwardCompatibility on
        rowKey = TableTestHelper.generateRandomKeyName();
        entity = new DateTestEntity(partitionKey, rowKey);
        entity.setDate(date);

        put = TableOperation.insertOrReplace(entity);
        this.table.execute(put);

        get = TableOperation.retrieve(partitionKey, rowKey, DateTestEntity.class);
        final TableRequestOptions options = new TableRequestOptions();
        options.setDateBackwardCompatibility(true);
        entity = this.table.execute(get, options, null).getResultAsType();
        assertEquals(date.getTime(), entity.getDate().getTime());

        // DateBackwardCompatibility off
        final String dateKey = "date";
        final EntityProperty property = new EntityProperty(date);
        rowKey = TableTestHelper.generateRandomKeyName();
        DynamicTableEntity dynamicEntity = new DynamicTableEntity(partitionKey, rowKey);
        dynamicEntity.getProperties().put(dateKey, property);

        put = TableOperation.insertOrReplace(dynamicEntity);
        this.table.execute(put);

        get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class);
        dynamicEntity = this.table.execute(get).getResultAsType();
        assertEquals(date.getTime(), dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime());

        // DateBackwardCompatibility on
        rowKey = TableTestHelper.generateRandomKeyName();
        dynamicEntity = new DynamicTableEntity(partitionKey, rowKey);
        dynamicEntity.getProperties().put(dateKey, property);

        put = TableOperation.insertOrReplace(dynamicEntity);
        this.table.execute(put);

        get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class);
        options.setDateBackwardCompatibility(true);
        dynamicEntity = this.table.execute(get, options, null).getResultAsType();
        assertEquals(date.getTime(), dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime());
    }

    private void testTableQueryRoundTripDate(final String dateString, final long milliseconds, final int ticks,
            final boolean writtenPre2, final boolean dateBackwardCompatibility, TablePayloadFormat format)
            throws URISyntaxException, StorageException {
        assertTrue(ticks >= 0);     // ticks is non-negative
        assertTrue(ticks <= 9999);  // ticks do not overflow into milliseconds
        final String partitionKey = "partitionTest";
        final String dateKey = "date";
        long expectedMilliseconds = milliseconds;

        if (dateBackwardCompatibility && (milliseconds % 1000 == 0) && (ticks < 1000)) {
            // when no milliseconds are present dateBackwardCompatibility causes up to 3 digits of ticks
            // to be read as milliseconds
            expectedMilliseconds += ticks;
        } else if (writtenPre2 && !dateBackwardCompatibility && (ticks == 0)) {
            // without DateBackwardCompatibility, milliseconds stored by Java prior to 0.4.0 are lost
            expectedMilliseconds -= expectedMilliseconds % 1000;
        }

        // Create a property for how the service would store the dateString
        EntityProperty property = new EntityProperty(dateString, EdmType.DATE_TIME);
        String rowKey = TableTestHelper.generateRandomKeyName();
        DynamicTableEntity dynamicEntity = new DynamicTableEntity(partitionKey, rowKey);
        dynamicEntity.getProperties().put(dateKey, property);

        // Add the entity to the table
        TableOperation put = TableOperation.insertOrReplace(dynamicEntity);
        this.table.execute(put);

        // Specify the options
        TableRequestOptions options = new TableRequestOptions();
        options.setDateBackwardCompatibility(dateBackwardCompatibility);
        options.setTablePayloadFormat(format);

        // Fetch the entity from the table
        TableOperation get = TableOperation.retrieve(partitionKey, rowKey, DynamicTableEntity.class);
        dynamicEntity = this.table.execute(get, options, null).getResultAsType();

        // Ensure the date matches our expectations
        assertEquals(expectedMilliseconds, dynamicEntity.getProperties().get(dateKey).getValueAsDate().getTime());
    }

    private static class DateTestEntity extends TableServiceEntity {
        private Date value;

        @SuppressWarnings("unused")
        public DateTestEntity() {
        }

        public DateTestEntity(String partition, String key) {
            super(partition, key);
        }

        public Date getDate() {
            return value;
        }

        public void setDate(Date value) {
            this.value = value;
        }
     }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class TableEscapingTests {

    private CloudTable table;

    @Before
    public void tableEscapingTestMethodSetUp() throws URISyntaxException, StorageException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();
    }

    @After
    public void tableEscapingTestMethodTearDown() throws StorageException {
        this.table.deleteIfExists();
    }

    @Test
    public void testEmptyString() throws StorageException {
        doEscapeTest("", false, true);
    }

    @Test
    public void testEmptyStringBatch() throws StorageException {
        doEscapeTest("", true, true);
    }

    @Test
    public void testRandomChars() throws StorageException {
        doEscapeTest("!$'\"()*+,;=", false);
    }

    @Test
    public void testRandomCharsBatch() throws StorageException {
        doEscapeTest("!$'\"()*+,;=", true);
    }

    @Test
    public void testPercent25() throws StorageException {
        doEscapeTest("foo%25", false, true);
    }

    @Test
    public void testPercent25Batch() throws StorageException {
        doEscapeTest("foo%25", true, true);
    }

    @Test
    public void testRegularPKInQuery() throws StorageException {
        doQueryEscapeTest("data");
    }

    @Test
    public void testSpecialChars() throws StorageException {
        doEscapeTest("\\ // @ ? <?", true);
    }

    @Test
    public void testSpecialCharsBatch() throws StorageException {
        doEscapeTest("\\ // @ ? <?", true);
    }

    @Test
    public void testUnicode() throws StorageException {
        doEscapeTest("\u00A9\u770b\u5168\u90e8", false, true);
        doEscapeTest("char中文test", false, true);
        doEscapeTest("charä¸­æ–‡test", false, true);
        doEscapeTest("世界你好", false, true);
    }

    @Test
    public void testUnicodeBatch() throws StorageException {
        doEscapeTest("\u00A9\u770b\u5168\u90e8", true, true);
        doEscapeTest("char中文test", true, true);
        doEscapeTest("charä¸­æ–‡test", true, true);
        doEscapeTest("世界你好", true, true);
    }

    @Test
    public void testUnicodeInQuery() throws StorageException {
        doQueryEscapeTest("char中文test");
        doQueryEscapeTest("charä¸­æ–‡test");
        doQueryEscapeTest("世界你好");
        doQueryEscapeTest("\u00A9\u770b\u5168\u90e8");
    }

    @Test
    public void testWhiteSpaceOnly() throws StorageException {
        doEscapeTest("     ", false, true);
    }

    @Test
    public void testWhiteSpaceOnlyBatch() throws StorageException {
        doEscapeTest("     ", true, true);
    }

    @Test
    public void testWhiteSpaceOnlyInQuery() throws StorageException {
        doQueryEscapeTest("     ");
    }

    @Test
    public void testXmlTest() throws StorageException {
        doEscapeTest("</>", false);
        doEscapeTest("<tag>", false);
        doEscapeTest("</entry>", false);
        doEscapeTest("!<", false);
        doEscapeTest("<!%^&j", false);
    }

    @Test
    public void testXmlTestBatch() throws StorageException {
        doEscapeTest("</>", false);
        doEscapeTest("<tag>", false);
        doEscapeTest("</entry>", false);
        doEscapeTest("!<", false);
        doEscapeTest("<!%^&j", false);
    }

    private void doEscapeTest(String data, boolean useBatch) throws StorageException {
        doEscapeTest(data, useBatch, false);
    }

    private void doEscapeTest(String data, boolean useBatch, boolean includeInKey) throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        doEscapeTestHelper(data, useBatch, includeInKey, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        doEscapeTestHelper(data, useBatch, includeInKey, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        doEscapeTestHelper(data, useBatch, includeInKey, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        options.setPropertyResolver(new Class1());
        doEscapeTestHelper(data, useBatch, includeInKey, options);
    }

    private void doEscapeTestHelper(String data, boolean useBatch, boolean includeInKey, TableRequestOptions options)
            throws StorageException {
        Class1 ref = new Class1();
        ref.setA(data);
        ref.setPartitionKey(includeInKey ? "temp" + data : "temp");
        ref.setRowKey(UUID.randomUUID().toString());

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.insert(ref);
            this.table.execute(batch, options, null);
        }
        else {
            this.table.execute(TableOperation.insert(ref), options, null);
        }

        TableResult res = null;

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class);
            res = this.table.execute(batch, options, null).get(0);
        }
        else {
            res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class),
                    options, null);
        }

        Class1 retObj = res.getResultAsType();
        assertEquals(ref.getA(), retObj.getA());
        assertEquals(ref.getPartitionKey(), retObj.getPartitionKey());

        ref.setEtag(retObj.getEtag());
        ref.setB(data);

        // Merge
        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.merge(ref);
            this.table.execute(batch, options, null);
        }
        else {
            this.table.execute(TableOperation.merge(ref), options, null);
        }

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class);
            res = this.table.execute(batch, options, null).get(0);
        }
        else {
            res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class),
                    options, null);
        }

        retObj = res.getResultAsType();
        assertEquals(ref.getA(), retObj.getA());
        assertEquals(ref.getB(), retObj.getB());

        // Replace
        ref.setEtag(retObj.getEtag());
        ref.setC(data);

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.replace(ref);
            this.table.execute(batch, options, null);
        }
        else {
            this.table.execute(TableOperation.replace(ref), options, null);
        }

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class);
            res = this.table.execute(batch, options, null).get(0);
        }
        else {
            res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class),
                    options, null);
        }

        retObj = res.getResultAsType();
        assertEquals(ref.getA(), retObj.getA());
        assertEquals(ref.getB(), retObj.getB());
        assertEquals(ref.getC(), retObj.getC());

        if (useBatch) {
            TableBatchOperation batch = new TableBatchOperation();
            batch.delete(retObj);
            res = this.table.execute(batch, options, null).get(0);
        }
        else {
            res = this.table.execute(TableOperation.delete(retObj), options, null);
        }
    }

    private void doQueryEscapeTest(String data) throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        doQueryEscapeTestHelper(data, options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        doQueryEscapeTestHelper(data, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        doQueryEscapeTestHelper(data, options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        options.setPropertyResolver(new Class1());
        doQueryEscapeTestHelper(data, options);
    }

    private void doQueryEscapeTestHelper(String data, TableRequestOptions options) throws StorageException {
        Class1 ref = new Class1();
        ref.setA(data);
        ref.setPartitionKey(UUID.randomUUID().toString());
        ref.setRowKey("foo");

        this.table.execute(TableOperation.insert(ref), options, null);
        TableQuery<Class1> query = TableQuery.from(Class1.class).where(
                String.format("(PartitionKey eq '%s') and (A eq '%s')", ref.getPartitionKey(), data));

        int count = 0;

        for (Class1 ent : this.table.execute(query, options, null)) {
            count++;
            assertEquals(ent.getA(), ref.getA());
            assertEquals(ent.getB(), ref.getB());
            assertEquals(ent.getC(), ref.getC());
            assertEquals(ent.getPartitionKey(), ref.getPartitionKey());
            assertEquals(ent.getRowKey(), ref.getRowKey());
        }

        assertEquals(count, 1);
    }
} @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
public class TableODataTests {

    TableRequestOptions options;
    DynamicTableEntity ent;

    private CloudTable table;

    @Before
    public void tableODataTestBeforeMethodSetUp() throws StorageException, URISyntaxException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();

        final CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        this.options = TableRequestOptions.populateAndApplyDefaults(this.options, tClient);
        this.options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);

        // Insert Entity
        this.ent = new DynamicTableEntity();
        this.ent.setPartitionKey("jxscl_odata");
        this.ent.setRowKey(UUID.randomUUID().toString());

        this.ent.getProperties().put("foo2", new EntityProperty("bar2"));
        this.ent.getProperties().put("foo", new EntityProperty("bar"));
        this.ent.getProperties().put("fooint", new EntityProperty(1234));

        this.table.execute(TableOperation.insert(this.ent), this.options, null);
    }

    @After
    public void tableODataTestBeforeMethodTearDown() throws StorageException {
        this.table.execute(TableOperation.delete(this.ent), this.options, null);
        this.table.deleteIfExists();
    }

    @Test
    public void testTableOperationRetrieveJsonNoMetadataFail() {

        // set custom property resolver
        this.options.setPropertyResolver(new CustomPropertyResolver());

        try {
            this.table.execute(TableOperation.retrieve(this.ent.getPartitionKey(), this.ent.getRowKey(), Class1.class),
                    this.options, null);
            fail("Invalid property resolver should throw");
        }
        catch (StorageException e) {
            assertEquals("Failed to parse property 'fooint' with value '1234' as type 'Edm.Guid'", e.getMessage());
        }
    }

    @Test
    public void testTableOperationRetrieveJsonNoMetadataResolverFail() {

        // set custom property resolver which throws
        this.options.setPropertyResolver(new ThrowingPropertyResolver());

        try {
            this.table.execute(TableOperation.retrieve(this.ent.getPartitionKey(), this.ent.getRowKey(), Class1.class),
                    this.options, null);
            fail("Invalid property resolver should throw");
        }
        catch (StorageException e) {
            assertEquals(
                    "The custom property resolver delegate threw an exception. Check the inner exception for more details.",
                    e.getMessage());
            assertTrue(e.getCause().getClass() == IllegalArgumentException.class);
        }
    }

    class CustomPropertyResolver implements PropertyResolver {
        @Override
        public EdmType propertyResolver(String pk, String rk, String key, String value) {
            if (key.equals("fooint")) {
                return EdmType.GUID;
            }

            return EdmType.STRING;
        }
    }

    class ThrowingPropertyResolver implements PropertyResolver {
        @Override
        public EdmType propertyResolver(String pk, String rk, String key, String value) {
            if (key.equals("fooint")) {
                throw new IllegalArgumentException();
            }

            return EdmType.STRING;
        }
    }
} @Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class TableOperationTests {

    private CloudTable table;

    @Before
    public void tableOperationTestMethodSetUp() throws URISyntaxException, StorageException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();
    }

    @After
    public void tableOperationTestMethodTearDown() throws StorageException {
        this.table.deleteIfExists();
    }

    @Test
    public void testPropertyCacheDisable() {
        try {
            TableServiceEntity.getReflectedEntityCache().put(this.getClass(), new HashMap<String, PropertyPair>());

            TableServiceEntity.setReflectedEntityCacheDisabled(true);
            assertEquals(true, TableServiceEntity.isReflectedEntityCacheDisabled());
            assertTrue(TableServiceEntity.getReflectedEntityCache().isEmpty());

            TableServiceEntity.setReflectedEntityCacheDisabled(false);
            assertEquals(false, TableServiceEntity.isReflectedEntityCacheDisabled());
        } finally {

            TableServiceEntity.setReflectedEntityCacheDisabled(false);
        }
    }

    @Test
    public void testRetrieveWithNullResolver() {
        try {
            TableOperation.retrieve("foo", "blah", (EntityResolver<?>) null);
        } catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(),
                    String.format(SR.ARGUMENT_NULL_OR_EMPTY, SR.QUERY_REQUIRES_VALID_CLASSTYPE_OR_RESOLVER));
        }
    }

    @Test
    public void testEntityWithSingleQuote() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        EmptyClass ref = new EmptyClass();
        ref.setPartitionKey("partition'key");
        ref.setRowKey("row'key");

        this.table.execute(TableOperation.insert(ref), options, null);
        this.table.execute(TableOperation.merge(ref), options, null);
        this.table.execute(TableOperation.insertOrReplace(ref), options, null);
        this.table.execute(TableOperation.insertOrMerge(ref), options, null);
        this.table.execute(TableOperation.replace(ref), options, null);
        this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), EmptyClass.class), options, null);
        this.table.execute(TableOperation.delete(ref), options, null);
    }

    @Test
    public void testInsertEntityWithoutPartitionKeyRowKey() {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        EmptyClass ref = new EmptyClass();
        ref.setPartitionKey("jxscl_odata");

        try {
            this.table.execute(TableOperation.insert(ref), options, null);
            fail("Inserts should not allow null row keys.");
        } catch (IllegalArgumentException e) {
            // continue, this is appropriate
        } catch (Exception e) {
            fail("Inserts with null row key should fail with an IllegalArgumentException thrown by assert not null.");
        }

        ref.setPartitionKey(null);
        ref.setRowKey(UUID.randomUUID().toString());
        try {
            this.table.execute(TableOperation.insert(ref), options, null);
            fail("Inserts should not allow null partition keys.");
        } catch (IllegalArgumentException e) {
            // continue, this is appropriate
        } catch (Exception e) {
            fail("Inserts with null partition key should fail with an IllegalArgumentException thrown by assert not null.");
        }
    }

    @Test
    public void testInsertEntityWithPropertyMoreThan255chars() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        DynamicTableEntity ref = new DynamicTableEntity();

        String propName = "";
        for (int m = 0; m < 256; m++) {
            propName = propName.concat("a");
        }

        ref.getProperties().put(propName, new EntityProperty("test"));
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        try {
            this.table.execute(TableOperation.insert(ref), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The property name exceeds the maximum allowed length (255)."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "PropertyNameTooLong");
        }
    }

    @Test
    public void testInsertEntityOver1MB() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        // 1mb right here
        ref.setD(new byte[1024 * 1024]);
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        try {
            this.table.execute(TableOperation.insert(ref), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The entity is larger than the maximum allowed size (1MB)."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityTooLarge");
        }
    }

    @Test
    public void testInsertEntityWithNumericProperty() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        DynamicTableEntity ref = new DynamicTableEntity();

        String propName = "";
        for (int m = 0; m < 255; m++) {
            propName = propName.concat(Integer.toString(m % 9));
        }

        ref.getProperties().put(propName, new EntityProperty("test"));
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        try {
            this.table.execute(TableOperation.insert(ref), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage().startsWith("The property name is invalid."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "PropertyNameInvalid");
        }
    }

    @Test
    public void testDeleteFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        this.table.execute(TableOperation.insert(ref), options, null);
        String oldEtag = ref.getEtag();

        // update entity
        ref.setA("updated");
        this.table.execute(TableOperation.replace(ref), options, null);

        ref.setEtag(oldEtag);

        try {
            this.table.execute(TableOperation.delete(ref), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied");
        }

        TableResult res2 = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        ref = res2.getResultAsType();
        // actually delete it
        this.table.execute(TableOperation.delete(ref), options, null);

        // now try to delete it and fail
        try {
            this.table.execute(TableOperation.delete(ref), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Not Found");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The specified resource does not exist."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound");
        }
    }

    @Test
    public void testMergeFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        // Insert base entity
        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        this.table.execute(TableOperation.insert(baseEntity), options, null);

        Class2 secondEntity = new Class2();
        secondEntity.setL("foo_L");
        secondEntity.setM("foo_M");
        secondEntity.setN("foo_N");
        secondEntity.setO("foo_O");
        secondEntity.setPartitionKey(baseEntity.getPartitionKey());
        secondEntity.setRowKey(baseEntity.getRowKey());
        secondEntity.setEtag(baseEntity.getEtag());
        String oldEtag = baseEntity.getEtag();

        this.table.execute(TableOperation.merge(secondEntity), options, null);

        secondEntity.setEtag(oldEtag);
        secondEntity.setL("updated");
        try {
            this.table.execute(TableOperation.merge(secondEntity), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied");
        }

        // retrieve entity
        TableResult queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        DynamicTableEntity retrievedEntity = queryResult.getResultAsType();
        this.table.execute(TableOperation.delete(retrievedEntity), options, null);

        try {
            this.table.execute(TableOperation.merge(secondEntity), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Not Found");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The specified resource does not exist."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound");
        }
    }

    @Test
    public void testInsertFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        TableOperation op = TableOperation.insert(ref);

        this.table.execute(op, options, null);
        try {
            this.table.execute(op, options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Conflict");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The specified entity already exists"));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityAlreadyExists");
        }
    }

    @Test
    public void testReplaceFail() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        // Insert entity
        this.table.execute(TableOperation.insert(baseEntity), options, null);

        String oldEtag = baseEntity.getEtag();

        TableResult queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        // Retrieve entity
        DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity>getResultAsType();
        assertNotNull("Property D", retrievedEntity.getProperties().get("D"));
        assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D").getValueAsByteArray()));

        // Remove property and update
        retrievedEntity.getProperties().remove("D");

        this.table.execute(TableOperation.replace(retrievedEntity), options, null);

        retrievedEntity.setEtag(oldEtag);

        try {
            this.table.execute(TableOperation.replace(retrievedEntity), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Precondition Failed");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The update condition specified in the request was not satisfied."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied");
        }

        // delete entity
        queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        this.table.execute(TableOperation.delete((DynamicTableEntity) queryResult.getResultAsType()), options, null);

        try {
            this.table.execute(TableOperation.replace(retrievedEntity), options, null);
            fail();
        } catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Not Found");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("The specified resource does not exist."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound");
        }
    }

    @Test
    public void testEmptyRetrieve() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        assertNull(res.getResult());
        assertEquals(res.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND);
    }

    @Test
    public void testInsertEmptyEntity() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        EmptyClass ref = new EmptyClass();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey("echo_default" + UUID.randomUUID().toString());

        TableResult res = this.table.execute(TableOperation.insert(ref), options, null);
        assertEquals(HttpURLConnection.HTTP_NO_CONTENT, res.getHttpStatusCode());

        EmptyClassDynamic refDynamic = new EmptyClassDynamic();
        refDynamic.setPartitionKey("jxscl_odata");
        refDynamic.setRowKey("echo_default" + UUID.randomUUID().toString());

        res = this.table.execute(TableOperation.insert(refDynamic), options, null);
        assertEquals(HttpURLConnection.HTTP_NO_CONTENT, res.getHttpStatusCode());
    }

    @Test
    public void testDelete() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testDelete(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testDelete(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testDelete(options);
    }

    private void testDelete(TableRequestOptions options) throws StorageException {
        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        TableOperation op = TableOperation.insert(ref);

        this.table.execute(op, options, null);
        this.table.execute(TableOperation.delete(ref), options, null);

        TableResult res2 = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        assertTrue(res2.getResult() == null);
    }

    @Test
    public void testInsertOrMerge() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testInsertOrMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testInsertOrMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testInsertOrMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testInsertOrMerge(options, true);
    }

    private void testInsertOrMerge(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        Class2 secondEntity = new Class2();
        secondEntity.setL("foo_L");
        secondEntity.setM("foo_M");
        secondEntity.setN("foo_N");
        secondEntity.setO("foo_O");
        secondEntity.setPartitionKey(baseEntity.getPartitionKey());
        secondEntity.setRowKey(baseEntity.getRowKey());
        secondEntity.setEtag(baseEntity.getEtag());

        // Insert or merge Entity - ENTITY DOES NOT EXIST NOW.
        TableResult insertResult = this.table.execute(TableOperation.insertOrMerge(baseEntity), options, null);

        assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        assertNotNull(insertResult.getEtag());

        // Insert or replace Entity - ENTITY EXISTS -> WILL MERGE
        this.table.execute(TableOperation.insertOrMerge(secondEntity), options, null);

        // Retrieve entity
        if (usePropertyResolver) {
            PropertyResolver resolver = new class1class2PropertyResolver();
            options.setPropertyResolver(resolver);
        }

        TableResult queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity>getResultAsType();

        assertNotNull("Property A", retrievedEntity.getProperties().get("A"));
        assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString());

        assertNotNull("Property B", retrievedEntity.getProperties().get("B"));
        assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString());

        assertNotNull("Property C", retrievedEntity.getProperties().get("C"));
        assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString());

        assertNotNull("Property D", retrievedEntity.getProperties().get("D"));
        assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D").getValueAsByteArray()));

        // Validate New properties exist
        assertNotNull("Property L", retrievedEntity.getProperties().get("L"));
        assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString());

        assertNotNull("Property M", retrievedEntity.getProperties().get("M"));
        assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString());

        assertNotNull("Property N", retrievedEntity.getProperties().get("N"));
        assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString());

        assertNotNull("Property O", retrievedEntity.getProperties().get("O"));
        assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString());
    }

    @Test
    public void testInsertOrReplace() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testInsertOrReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testInsertOrReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testInsertOrReplace(options);
    }

    private void testInsertOrReplace(TableRequestOptions options) throws StorageException {
        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        Class2 secondEntity = new Class2();
        secondEntity.setL("foo_L");
        secondEntity.setM("foo_M");
        secondEntity.setN("foo_N");
        secondEntity.setO("foo_O");
        secondEntity.setPartitionKey(baseEntity.getPartitionKey());
        secondEntity.setRowKey(baseEntity.getRowKey());
        secondEntity.setEtag(baseEntity.getEtag());

        // Insert or replace Entity - ENTITY DOES NOT EXIST NOW.
        TableResult insertResult = this.table.execute(TableOperation.insertOrReplace(baseEntity), options, null);

        assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        assertNotNull(insertResult.getEtag());

        // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE
        this.table.execute(TableOperation.insertOrReplace(secondEntity), options, null);

        // Retrieve entity
        TableResult queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        DynamicTableEntity retrievedEntity = queryResult.getResultAsType();

        // Validate old properties dont exist
        assertTrue(retrievedEntity.getProperties().get("A") == null);
        assertTrue(retrievedEntity.getProperties().get("B") == null);
        assertTrue(retrievedEntity.getProperties().get("C") == null);
        assertTrue(retrievedEntity.getProperties().get("D") == null);

        // Validate New properties exist
        assertNotNull("Property L", retrievedEntity.getProperties().get("L"));
        assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString());

        assertNotNull("Property M", retrievedEntity.getProperties().get("M"));
        assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString());

        assertNotNull("Property N", retrievedEntity.getProperties().get("N"));
        assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString());

        assertNotNull("Property O", retrievedEntity.getProperties().get("O"));
        assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString());
    }

    @Test
    public void testMerge() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testMerge(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testMerge(options, true);
    }

    private void testMerge(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        // Insert base entity
        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        this.table.execute(TableOperation.insert(baseEntity), options, null);

        Class2 secondEntity = new Class2();
        secondEntity.setL("foo_L");
        secondEntity.setM("foo_M");
        secondEntity.setN("foo_N");
        secondEntity.setO("foo_O");
        secondEntity.setPartitionKey(baseEntity.getPartitionKey());
        secondEntity.setRowKey(baseEntity.getRowKey());
        secondEntity.setEtag(baseEntity.getEtag());

        TableResult mergeResult = this.table.execute(TableOperation.merge(secondEntity), options, null);

        assertEquals(mergeResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        assertNotNull(mergeResult.getEtag());

        // retrieve result
        if (usePropertyResolver) {
            PropertyResolver resolver = new class1class2PropertyResolver();
            options.setPropertyResolver(resolver);
        }

        TableResult res2 = this.table.execute(TableOperation.retrieve(secondEntity.getPartitionKey(),
                secondEntity.getRowKey(), DynamicTableEntity.class), options, null);
        DynamicTableEntity mergedEntity = (DynamicTableEntity) res2.getResult();

        assertNotNull("Property A", mergedEntity.getProperties().get("A"));
        assertEquals(baseEntity.getA(), mergedEntity.getProperties().get("A").getValueAsString());

        assertNotNull("Property B", mergedEntity.getProperties().get("B"));
        assertEquals(baseEntity.getB(), mergedEntity.getProperties().get("B").getValueAsString());

        assertNotNull("Property C", mergedEntity.getProperties().get("C"));
        assertEquals(baseEntity.getC(), mergedEntity.getProperties().get("C").getValueAsString());

        assertNotNull("Property D", mergedEntity.getProperties().get("D"));
        assertTrue(Arrays.equals(baseEntity.getD(), mergedEntity.getProperties().get("D").getValueAsByteArray()));

        assertNotNull("Property L", mergedEntity.getProperties().get("L"));
        assertEquals(secondEntity.getL(), mergedEntity.getProperties().get("L").getValueAsString());

        assertNotNull("Property M", mergedEntity.getProperties().get("M"));
        assertEquals(secondEntity.getM(), mergedEntity.getProperties().get("M").getValueAsString());

        assertNotNull("Property N", mergedEntity.getProperties().get("N"));
        assertEquals(secondEntity.getN(), mergedEntity.getProperties().get("N").getValueAsString());

        assertNotNull("Property O", mergedEntity.getProperties().get("O"));
        assertEquals(secondEntity.getO(), mergedEntity.getProperties().get("O").getValueAsString());
    }

    @Test
    public void testReplace() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testReplace(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testReplace(options);
    }

    private void testReplace(TableRequestOptions options) throws StorageException {
        Class1 baseEntity = new Class1();
        baseEntity.setA("foo_A");
        baseEntity.setB("foo_B");
        baseEntity.setC("foo_C");
        baseEntity.setD(new byte[]{0, 1, 2});
        baseEntity.setPartitionKey("jxscl_odata");
        baseEntity.setRowKey(UUID.randomUUID().toString());

        // Insert entity
        this.table.execute(TableOperation.insert(baseEntity), options, null);

        TableResult queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);
        // Retrieve entity
        DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity>getResultAsType();
        assertNotNull("Property D", retrievedEntity.getProperties().get("D"));
        assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D").getValueAsByteArray()));

        // Remove property and update
        retrievedEntity.getProperties().remove("D");

        TableResult replaceResult = this.table.execute(TableOperation.replace(retrievedEntity), options, null);

        assertEquals(replaceResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT);
        assertNotNull(replaceResult.getEtag());

        // Retrieve Entity
        queryResult = this.table
                .execute(TableOperation.retrieve(baseEntity.getPartitionKey(), baseEntity.getRowKey(),
                        DynamicTableEntity.class), options, null);

        retrievedEntity = queryResult.<DynamicTableEntity>getResultAsType();

        // Validate
        assertNotNull("Property A", retrievedEntity.getProperties().get("A"));
        assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString());

        assertNotNull("Property B", retrievedEntity.getProperties().get("B"));
        assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString());

        assertNotNull("Property C", retrievedEntity.getProperties().get("C"));
        assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString());

        assertTrue(retrievedEntity.getProperties().get("D") == null);
    }

    @Test
    public void testInsert() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testInsert(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testInsert(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testInsert(options);
    }

    private void testInsert(TableRequestOptions options) throws StorageException {
        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        // 1mb right here
        ref.setD(new byte[1024]);
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey("echo_default" + UUID.randomUUID().toString());

        TableResult res = this.table.execute(TableOperation.insert(ref), options, null);
        assertEquals(HttpURLConnection.HTTP_NO_CONTENT, res.getHttpStatusCode());

        ref.setRowKey("echo" + UUID.randomUUID().toString());
        res = this.table.execute(TableOperation.insert(ref, true), options, null);
        assertEquals(HttpURLConnection.HTTP_CREATED, res.getHttpStatusCode());

        ref.setRowKey("echo_off" + UUID.randomUUID().toString());
        res = this.table.execute(TableOperation.insert(ref, false), options, null);
        assertEquals(HttpURLConnection.HTTP_NO_CONTENT, res.getHttpStatusCode());
    }

    @Test
    public void testRetrieveWithoutEntityResolver() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testRetrieveWithoutEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testRetrieveWithoutEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testRetrieveWithoutEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testRetrieveWithoutEntityResolver(options, true);
    }

    private void testRetrieveWithoutEntityResolver(TableRequestOptions options, boolean usePropertyResolver)
            throws StorageException {
        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        // with cache on
        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        Class1 retrievedEnt = res.getResultAsType();

        assertEquals(ref.getA(), retrievedEnt.getA());
        assertTrue(Arrays.equals(ref.getD(), retrievedEnt.getD()));

        // with cache off
        TableServiceEntity.setReflectedEntityCacheDisabled(true);
        try {
            res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class),
                    options, null);

            retrievedEnt = res.getResultAsType();

            assertEquals(ref.getA(), retrievedEnt.getA());
            assertTrue(Arrays.equals(ref.getD(), retrievedEnt.getD()));
        } finally {
            TableServiceEntity.setReflectedEntityCacheDisabled(false);
        }
    }

    @Test
    public void testRetrieveWithEntityResolver() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testRetrieveWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testRetrieveWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testRetrieveWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testRetrieveWithEntityResolver(options, true);
    }

    private void testRetrieveWithEntityResolver(TableRequestOptions options, boolean usePropertyResolver)
            throws StorageException {
        Class1 ref = new Class1();
        ref.setA("foo_A");
        ref.setB("foo_B");
        ref.setC("foo_C");
        ref.setD(new byte[]{0, 1, 2});
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        // with cache on
        TableOperation op = TableOperation.insert(ref);

        this.table.execute(op, options, null);

        TableResult res4 = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), new EntityResolver<Class1>() {
                    @Override
                    public Class1 resolve(String partitionKey, String rowKey, Date timeStamp,
                                          HashMap<String, EntityProperty> properties, String etag) {
                        Class1 result = new Class1();
                        result.setA(properties.get("A").getValueAsString());
                        result.setD(properties.get("D").getValueAsByteArray());
                        return result;
                    }
                }), options, null);

        Class1 retrievedEnt = (Class1) res4.getResult();
        assertEquals(ref.getA(), retrievedEnt.getA());
        assertTrue(Arrays.equals(ref.getD(), retrievedEnt.getD()));

        // with cache off
        TableServiceEntity.setReflectedEntityCacheDisabled(true);
        try {
            res4 = this.table.execute(
                    TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), new EntityResolver<Class1>() {
                        @Override
                        public Class1 resolve(String partitionKey, String rowKey, Date timeStamp,
                                              HashMap<String, EntityProperty> properties, String etag) {
                            Class1 result = new Class1();
                            result.setA(properties.get("A").getValueAsString());
                            result.setD(properties.get("D").getValueAsByteArray());
                            return result;
                        }
                    }), options, null);

            retrievedEnt = (Class1) res4.getResult();
            assertEquals(ref.getA(), retrievedEnt.getA());
            assertTrue(Arrays.equals(ref.getD(), retrievedEnt.getD()));
        } finally {
            TableServiceEntity.setReflectedEntityCacheDisabled(false);
        }
    }
} @Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class TableQueryTests {

    private static CloudTable table;

    @BeforeClass
    public static void tableQueryTestMethodSetUp() throws Exception {
        table = TableTestHelper.getRandomTableReference();
        table.createIfNotExists();

        // Insert 500 entities in Batches to query
        for (int i = 0; i < 5; i++) {
            TableBatchOperation batch = new TableBatchOperation();

            for (int j = 0; j < 100; j++) {
                Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i));
                ent.setRowKey(String.format("%06d", j));
                batch.insert(ent);
            }

            table.execute(batch);
        }
    }

    @AfterClass
    public static void tableQueryTestMethodTearDown() throws Exception {
        table.deleteIfExists();
    }

    @Test
    public void testQueryWithNullClassType()  {
        try {
            TableQuery.from(null);
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), String.format(SR.ARGUMENT_NULL_OR_EMPTY, "class type"));
        }
    }

    @Test
    public void testQueryWithInvalidTakeCount()  {
        try {
            TableQuery.from(TableServiceEntity.class).take(0);
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), "Take count must be positive and greater than 0.");
        }

        try {
            TableQuery.from(TableServiceEntity.class).take(-1);
        }
        catch (IllegalArgumentException ex) {
            assertEquals(ex.getMessage(), "Take count must be positive and greater than 0.");
        }
    }

    @Test
    public void testTableWithSelectOnMissingFields() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableWithSelectOnMissingFields(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableWithSelectOnMissingFields(options);
    }

    private void testTableWithSelectOnMissingFields(TableRequestOptions options) throws StorageException {
        TableQuery<DynamicTableEntity> projectionQuery = TableQuery.from(DynamicTableEntity.class).where(
                "(PartitionKey eq 'javatables_batch_0') and (RowKey eq '000000')");

        // A exists, F does not
        projectionQuery.select(new String[]{"A", "F"});

        ResultSegment<DynamicTableEntity> seg = table.executeSegmented(projectionQuery, null, options, null);
        assertEquals(1, seg.getResults().size());

        DynamicTableEntity ent = seg.getResults().get(0);
        assertEquals("foo_A", ent.getProperties().get("A").getValueAsString());
        assertEquals(null, ent.getProperties().get("F").getValueAsString());
        assertEquals(EdmType.STRING, ent.getProperties().get("F").getEdmType());
    }

    public void testTableQueryProjectionWithNull() throws URISyntaxException, StorageException {
        CloudTable table = TableTestHelper.getRandomTableReference();
        try {
            // Create a new table so we don't pollute the main query table
            table.createIfNotExists();

            // Insert an entity which is missing String and IntegerPrimitive
            DynamicTableEntity entity = new DynamicTableEntity(UUID.randomUUID().toString(), UUID.randomUUID()
                    .toString());
            table.execute(TableOperation.insert(entity));

            testTableQueryProjectionWithSpecialCases(table);
        }
        finally {
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableQueryProjectionWithIncorrectTypes() throws URISyntaxException, StorageException {
        CloudTable table = TableTestHelper.getRandomTableReference();
        try {
            // Create a new table so we don't pollute the main query table
            table.createIfNotExists();

            // Insert an entity with String as an int, and IntegerPrimitive as a bool
            DynamicTableEntity entity = new DynamicTableEntity(UUID.randomUUID().toString(), UUID.randomUUID()
                    .toString());
            entity.getProperties().put("String", new EntityProperty(1234));
            entity.getProperties().put("IntegerPrimitive", new EntityProperty(true));
            table.execute(TableOperation.insert(entity));

            testTableQueryProjectionWithSpecialCases(table);
        }
        finally {
            table.deleteIfExists();
        }
    }

    private void testTableQueryProjectionWithSpecialCases(CloudTable table) {
        // Query on String and IntegerPrimitive
        TableQuery<ComplexEntity> query = TableQuery.from(ComplexEntity.class).select(
                new String[] { "String", "IntegerPrimitive"});
        Iterable<ComplexEntity> iterable = table.execute(query);

        List<ComplexEntity> entities = new ArrayList<ComplexEntity>();
        for (ComplexEntity entity : iterable) {
            entities.add(entity);
        }

        // Verify A has a set value and B and E have class defaults
        assertEquals(1, entities.size());
        ComplexEntity entity = entities.get(0);
        assertNull(entity.getString());
        assertEquals(-1, entity.getIntegerPrimitive());
    }

    @Test
    public void testTableQueryWithSpecialChars() throws StorageException, URISyntaxException {
        CloudTable table = TableTestHelper.getRandomTableReference();

        try {
            table.createIfNotExists();

            testTableQueryWithSpecialChars('\'', table);
            testTableQueryWithSpecialChars('=', table);
            testTableQueryWithSpecialChars('_', table);
            testTableQueryWithSpecialChars(' ', table);
            testTableQueryWithSpecialChars('界', table);
        }
        finally {
            table.deleteIfExists();
        }
    }

    private void testTableQueryWithSpecialChars(char charToTest, CloudTable table)
            throws StorageException, URISyntaxException {
        String partitionKey = "partition" + charToTest + "key";
        String rowKey = "row" + charToTest + "key";

        EmptyClass ref = new EmptyClass();
        ref.setPartitionKey(partitionKey);
        ref.setRowKey(rowKey);

        table.execute(TableOperation.insert(ref));
        String condition = TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, partitionKey);
        ResultSegment<EmptyClass> seg = table.executeSegmented(TableQuery.from(EmptyClass.class).where(condition), null);

        assertEquals(1, seg.getLength());
        assertEquals(partitionKey, seg.getResults().get(0).getPartitionKey());
    }

    @Test
    public void testTableInvalidQuery() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();
        options.setTablePayloadFormat(TablePayloadFormat.Json);

        TableQuery<Class1> query = TableQuery.from(Class1.class).where(
                String.format("(PartitionKey ) and (RowKey ge '%s')", "000050"));
        try {
            table.executeSegmented(query, null, options, null);
            fail();
        }
        catch (TableServiceException ex) {
            assertEquals(ex.getMessage(), "Bad Request");
            assertTrue(ex.getExtendedErrorInformation().getErrorMessage()
                    .startsWith("A binary operator with incompatible types was detected. Found operand types 'Edm.String' and 'Edm.Boolean' for operator kind 'And'."));
            assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "InvalidInput");
        }
    }

    @Test
    public void testQueryOnSupportedTypes() throws InterruptedException, StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testQueryOnSupportedTypes(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testQueryOnSupportedTypes(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testQueryOnSupportedTypes(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testQueryOnSupportedTypes(options, true);
    }

    private void testQueryOnSupportedTypes(TableRequestOptions options, boolean usePropertyResolver)
            throws InterruptedException, StorageException {
        // Setup
        TableBatchOperation batch = new TableBatchOperation();
        String pk = UUID.randomUUID().toString();

        long maxInt = Integer.MAX_VALUE; // used to ensure the test sends longs that cannot be interpreted as ints

        ComplexEntity middleRef = null;

        for (int j = 0; j < 100; j++) {
            ComplexEntity ent = new ComplexEntity();
            ent.setPartitionKey(pk);
            ent.setRowKey(String.format("%04d", j));
            ent.setBinary(new Byte[] { 0x01, 0x02, (byte) j });
            ent.setBinaryPrimitive(new byte[] { 0x01, 0x02, (byte) j });
            ent.setBool(j % 2 == 0 ? true : false);
            ent.setBoolPrimitive(j % 2 == 0 ? true : false);
            ent.setDateTime(new Date());
            ent.setDouble(j + ((double) j) / 100);
            ent.setDoublePrimitive(j + ((double) j) / 100);
            ent.setInt32(j);
            ent.setInt64(j + maxInt);
            ent.setIntegerPrimitive(j);
            ent.setLongPrimitive(j + maxInt);
            ent.setGuid(UUID.randomUUID());
            ent.setString(String.format("%04d", j));

            // Add delay to make times unique
            Thread.sleep(50);
            batch.insert(ent);
            if (j == 50) {
                middleRef = ent;
            }
        }

        if (usePropertyResolver) {
            options.setPropertyResolver(middleRef);
        }

        table.execute(batch, options, null);

        try {
            // 1. Filter on String
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("String", QueryComparisons.GREATER_THAN_OR_EQUAL, "0050"), 50,
                    options, usePropertyResolver);

            // 2. Filter on UUID
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Guid", QueryComparisons.EQUAL, middleRef.getGuid()), 1,
                    options, usePropertyResolver);

            // 3. Filter on Long
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Int64", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getInt64()), 50, options, usePropertyResolver);

            executeQueryAndAssertResults(TableQuery.generateFilterCondition("LongPrimitive",
                    QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getInt64()), 50, options, usePropertyResolver);

            // 4. Filter on Double
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Double", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getDouble()), 50, options, usePropertyResolver);

            executeQueryAndAssertResults(TableQuery.generateFilterCondition("DoublePrimitive",
                    QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getDouble()), 50, options, usePropertyResolver);

            // 5. Filter on Integer
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Int32", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getInt32()), 50, options, usePropertyResolver);

            executeQueryAndAssertResults(TableQuery.generateFilterCondition("IntegerPrimitive",
                    QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getInt32()), 50, options, usePropertyResolver);

            // 6. Filter on Date
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("DateTime", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getDateTime()), 50, options, usePropertyResolver);

            // 7. Filter on Boolean
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Bool", QueryComparisons.EQUAL, middleRef.getBool()), 50,
                    options, usePropertyResolver);

            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("BoolPrimitive", QueryComparisons.EQUAL, middleRef.getBool()),
                    50, options, usePropertyResolver);

            // 8. Filter on Binary
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Binary", QueryComparisons.EQUAL, middleRef.getBinary()), 1,
                    options, usePropertyResolver);

            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("BinaryPrimitive", QueryComparisons.EQUAL,
                            middleRef.getBinaryPrimitive()), 1, options, usePropertyResolver);

            // 9. Filter on Binary GTE
            executeQueryAndAssertResults(
                    TableQuery.generateFilterCondition("Binary", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getBinary()), 50, options, usePropertyResolver);

            executeQueryAndAssertResults(TableQuery.generateFilterCondition("BinaryPrimitive",
                    QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getBinaryPrimitive()), 50, options,
                    usePropertyResolver);

            // 10. Complex Filter on Binary GTE
            executeQueryAndAssertResults(TableQuery.combineFilters(
                    TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL,
                            middleRef.getPartitionKey()),
                    TableQuery.Operators.AND,
                    TableQuery.generateFilterCondition("Binary", QueryComparisons.GREATER_THAN_OR_EQUAL,
                            middleRef.getBinary())), 50, options, usePropertyResolver);

            executeQueryAndAssertResults(TableQuery.generateFilterCondition("BinaryPrimitive",
                    QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getBinaryPrimitive()), 50, options,
                    usePropertyResolver);

        }
        finally {
            // cleanup
            TableBatchOperation delBatch = new TableBatchOperation();
            TableQuery<ComplexEntity> query = TableQuery.from(ComplexEntity.class).where(
                    String.format("PartitionKey eq '%s'", pk));

            for (ComplexEntity e : table.execute(query, options, null)) {
                delBatch.delete(e);
            }

            table.execute(delBatch, options, null);
        }
    }

    private void executeQueryAndAssertResults(String filter, int expectedResults, TableRequestOptions options,
            boolean usePropertyResolver)  {
        // instantiate class to use property resolver
        ComplexEntity ent = new ComplexEntity();
        if (usePropertyResolver) {
            options.setPropertyResolver(ent);
        }

        int count = 0;
        TableQuery<ComplexEntity> query = TableQuery.from(ComplexEntity.class).where(filter);
        for (@SuppressWarnings("unused")
        ComplexEntity e : table.execute(query, options, null)) {
            count++;
        }

        assertEquals(expectedResults, count);
    }

    @Test
    public void testTableQueryIterateTwice()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryIterateTwice(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryIterateTwice(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryIterateTwice(options);
    }

    private void testTableQueryIterateTwice(TableRequestOptions options)  {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        final Iterable<DynamicTableEntity> result = table.execute(TableQuery.from(DynamicTableEntity.class).take(50),
                options, null);

        ArrayList<DynamicTableEntity> firstIteration = new ArrayList<DynamicTableEntity>();
        ArrayList<DynamicTableEntity> secondIteration = new ArrayList<DynamicTableEntity>();

        // Validate results
        for (DynamicTableEntity ent : result) {
            assertEquals(ent.getProperties().size(), 4);
            assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA());
            assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB());
            assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC());
            assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD()));
            firstIteration.add(ent);
        }

        // Validate results
        for (DynamicTableEntity ent : result) {
            assertEquals(ent.getProperties().size(), 4);
            assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA());
            assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB());
            assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC());
            assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD()));
            secondIteration.add(ent);
        }

        assertEquals(firstIteration.size(), secondIteration.size());
        for (int m = 0; m < firstIteration.size(); m++) {
            assertEquals(firstIteration.get(m).getPartitionKey(), secondIteration.get(m).getPartitionKey());
            assertEquals(firstIteration.get(m).getRowKey(), secondIteration.get(m).getRowKey());
            assertEquals(firstIteration.get(m).getProperties().size(), secondIteration.get(m).getProperties().size());
            assertEquals(firstIteration.get(m).getProperties().get("A").getValueAsString(), secondIteration.get(m)
                    .getProperties().get("A").getValueAsString());
            assertEquals(firstIteration.get(m).getProperties().get("B").getValueAsString(), secondIteration.get(m)
                    .getProperties().get("B").getValueAsString());
            assertEquals(firstIteration.get(m).getProperties().get("C").getValueAsString(), secondIteration.get(m)
                    .getProperties().get("C").getValueAsString());
            assertTrue(Arrays.equals(firstIteration.get(m).getProperties().get("D").getValueAsByteArray(),
                    secondIteration.get(m).getProperties().get("D").getValueAsByteArray()));
        }
    }

    @Test
    public void testTableQueryWithDynamicEntity() {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithDynamicEntity(options);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithDynamicEntity(options);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithDynamicEntity(options);
    }

    private void testTableQueryWithDynamicEntity(TableRequestOptions options)  {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        final Iterable<DynamicTableEntity> result = table.execute(TableQuery.from(DynamicTableEntity.class), options,
                null);

        // Validate results
        for (DynamicTableEntity ent : result) {
            assertEquals(ent.getProperties().size(), 4);
            assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA());
            assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB());
            assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC());
            assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD()));
        }
    }

    @Test
    public void testTableQueryWithProjection()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithProjection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithProjection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithProjection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithProjection(options, true);
    }

    private void testTableQueryWithProjection(TableRequestOptions options, boolean usePropertyResolver)
             {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        final Iterable<Class1> result = table.execute(TableQuery.from(Class1.class).select(new String[] { "A", "C" }),
                options, null);

        // Validate results
        for (Class1 ent : result) {
            // Validate core properties were sent.
            assertNotNull(ent.getPartitionKey());
            assertNotNull(ent.getRowKey());
            assertNotNull(ent.getTimestamp());

            // Validate correct column returned.
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), null);
            assertEquals(ent.getC(), randEnt.getC());
            assertEquals(ent.getD(), null);
        }
    }

    @Test
    public void testSelectOnlySendsReservedColumnsOnce()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testSelectOnlySendsReservedColumnsOnce(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testSelectOnlySendsReservedColumnsOnce(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testSelectOnlySendsReservedColumnsOnce(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testSelectOnlySendsReservedColumnsOnce(options, true);
    }

    private void testSelectOnlySendsReservedColumnsOnce(TableRequestOptions options, boolean usePropertyResolver)
             {
        // Create entity to use property resolver
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        OperationContext opContext = new OperationContext();
        opContext.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() {

            @Override
            public void eventOccurred(ResponseReceivedEvent eventArg) {
                HttpURLConnection conn = (HttpURLConnection) eventArg.getConnectionObject();

                String urlString = conn.getURL().toString();

                assertEquals(urlString.indexOf("PartitionKey"), urlString.lastIndexOf("PartitionKey"));
                assertEquals(urlString.indexOf("RowKey"), urlString.lastIndexOf("RowKey"));
                assertEquals(urlString.indexOf("Timestamp"), urlString.lastIndexOf("Timestamp"));
            }
        });

        final Iterable<Class1> result = table.execute(
                TableQuery.from(Class1.class).select(new String[] { "PartitionKey", "RowKey", "Timestamp" }), options,
                opContext);

        // Validate results
        for (Class1 ent : result) {
            assertEquals(ent.getA(), null);
            assertEquals(ent.getB(), null);
            assertEquals(ent.getC(), null);
            assertEquals(ent.getD(), null);
        }
    }

    @Test
    public void testTableQueryWithReflection()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithReflection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithReflection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithReflection(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithReflection(options, true);
    }

    private void testTableQueryWithReflection(TableRequestOptions options, boolean usePropertyResolver)
             {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        final Iterable<Class1> result = table.execute(TableQuery.from(Class1.class), options, null);

        // Validate results
        for (Class1 ent : result) {
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), randEnt.getB());
            assertEquals(ent.getC(), randEnt.getC());
            assertTrue(Arrays.equals(ent.getD(), randEnt.getD()));
        }
    }

    @Test
    public void testTableQueryWithEntityResolver()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithEntityResolver(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithEntityResolver(options, true);
    }

    private void testTableQueryWithEntityResolver(TableRequestOptions options, boolean usePropertyResolver)
             {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        final Iterable<Class1> result = table.execute(TableQuery.from(TableServiceEntity.class),
                new EntityResolver<Class1>() {
                    @Override
                    public Class1 resolve(String partitionKey, String rowKey, Date timeStamp,
                            HashMap<String, EntityProperty> properties, String etag) {
                        assertEquals(properties.size(), 4);
                        Class1 ref = new Class1();
                        ref.setA(properties.get("A").getValueAsString());
                        ref.setB(properties.get("B").getValueAsString());
                        ref.setC(properties.get("C").getValueAsString());
                        ref.setD(properties.get("D").getValueAsByteArray());
                        return ref;
                    }
                }, options, null);

        // Validate results
        for (Class1 ent : result) {
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), randEnt.getB());
            assertEquals(ent.getC(), randEnt.getC());
            assertTrue(Arrays.equals(ent.getD(), randEnt.getD()));
        }
    }

    @Test
    public void testTableQueryWithTake() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithTake(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithTake(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithTake(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithTake(options, true);
    }

    private void testTableQueryWithTake(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        // Create entity to check against
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        final ResultSegment<Class1> result = table.executeSegmented(
                TableQuery.from(Class1.class).select(new String[] { "A", "C" }).take(25), null, options, null);

        int count = 0;
        // Validate results
        for (Class1 ent : result.getResults()) {
            count++;
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), null);
            assertEquals(ent.getC(), randEnt.getC());
            assertEquals(ent.getD(), null);
        }

        assertEquals(count, 25);
    }

    @Test
    public void testTableQueryWithFilter()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithFilter(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithFilter(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithFilter(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithFilter(options, true);
    }

    private void testTableQueryWithFilter(TableRequestOptions options, boolean usePropertyResolver)
             {
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        TableQuery<Class1> query = TableQuery.from(Class1.class).where(
                String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050"));

        int count = 0;

        for (Class1 ent : table.execute(query, options, null)) {
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), randEnt.getB());
            assertEquals(ent.getC(), randEnt.getC());
            assertEquals(ent.getPartitionKey(), "javatables_batch_1");
            assertEquals(ent.getRowKey(), String.format("%06d", count + 50));
            count++;
        }

        assertEquals(50, count);
    }

    @Test
    public void testTableQueryWithContinuation()  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testTableQueryWithContinuation(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testTableQueryWithContinuation(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithContinuation(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testTableQueryWithContinuation(options, true);
    }

    private void testTableQueryWithContinuation(TableRequestOptions options, boolean usePropertyResolver)
             {
        Class1 randEnt = TableTestHelper.generateRandomEntity(null);

        if (usePropertyResolver) {
            options.setPropertyResolver(randEnt);
        }

        TableQuery<Class1> query = TableQuery.from(Class1.class)
                .where(String.format("(PartitionKey ge '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050"))
                .take(25);

        // take will cause the query to return 25 at a time

        int count = 0;
        int pk = 1;
        for (Class1 ent : table.execute(query, options, null)) {
            assertEquals(ent.getA(), randEnt.getA());
            assertEquals(ent.getB(), randEnt.getB());
            assertEquals(ent.getC(), randEnt.getC());
            assertEquals(ent.getPartitionKey(), "javatables_batch_" + Integer.toString(pk));
            assertEquals(ent.getRowKey(), String.format("%06d", count % 50 + 50));
            count++;

            if (count % 50 == 0) {
                pk++;
            }
        }

        assertEquals(count, 200);
    }
} @Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class TableSerializerTests {

    private CloudTable table;

    @Before
    public void tableSerializerTestMethodSetUp() throws URISyntaxException, StorageException {
        this.table = TableTestHelper.getRandomTableReference();
        this.table.createIfNotExists();
    }

    @After
    public void tableSerializerTestMethodTearDown() throws StorageException {
        this.table.deleteIfExists();
    }

    @Test
    public void testIgnoreAnnotation() throws StorageException {
        // Ignore On Getter
        IgnoreOnGetter ignoreGetter = new IgnoreOnGetter();
        ignoreGetter.setPartitionKey("jxscl_odata");
        ignoreGetter.setRowKey(UUID.randomUUID().toString());
        ignoreGetter.setIgnoreString("ignore data");

        this.table.execute(TableOperation.insert(ignoreGetter));

        TableResult res = this.table.execute(TableOperation.retrieve(ignoreGetter.getPartitionKey(),
                ignoreGetter.getRowKey(), IgnoreOnGetter.class));

        IgnoreOnGetter retrievedIgnoreG = res.getResultAsType();
        assertEquals(retrievedIgnoreG.getIgnoreString(), null);

        // Ignore On Setter
        IgnoreOnSetter ignoreSetter = new IgnoreOnSetter();
        ignoreSetter.setPartitionKey("jxscl_odata");
        ignoreSetter.setRowKey(UUID.randomUUID().toString());
        ignoreSetter.setIgnoreString("ignore data");

        this.table.execute(TableOperation.insert(ignoreSetter));

        res = this.table.execute(TableOperation.retrieve(ignoreSetter.getPartitionKey(), ignoreSetter.getRowKey(),
                IgnoreOnSetter.class));

        IgnoreOnSetter retrievedIgnoreS = res.getResultAsType();
        assertEquals(retrievedIgnoreS.getIgnoreString(), null);

        // Ignore On Getter AndSetter
        IgnoreOnGetterAndSetter ignoreGetterSetter = new IgnoreOnGetterAndSetter();
        ignoreGetterSetter.setPartitionKey("jxscl_odata");
        ignoreGetterSetter.setRowKey(UUID.randomUUID().toString());
        ignoreGetterSetter.setIgnoreString("ignore data");

        this.table.execute(TableOperation.insert(ignoreGetterSetter));

        res = this.table.execute(TableOperation.retrieve(ignoreGetterSetter.getPartitionKey(),
                ignoreGetterSetter.getRowKey(), IgnoreOnGetterAndSetter.class));

        IgnoreOnGetterAndSetter retrievedIgnoreGS = res.getResultAsType();
        assertEquals(retrievedIgnoreGS.getIgnoreString(), null);
    }

    @Test
    public void testStoreAsAnnotation() throws StorageException {
        StoreAsEntity ref = new StoreAsEntity();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());
        ref.setStoreAsString("StoreAsOverride Data");
        ref.populateEntity();

        this.table.execute(TableOperation.insert(ref));

        TableResult res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(),
                StoreAsEntity.class));

        StoreAsEntity retrievedStoreAsRef = res.getResultAsType();
        assertEquals(retrievedStoreAsRef.getStoreAsString(), ref.getStoreAsString());

        // Same query with a class without the storeAs annotation
        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class));

        ComplexEntity retrievedComplexRef = res.getResultAsType();
        assertEquals(retrievedComplexRef.getString(), ref.getStoreAsString());

        this.table.execute(TableOperation.delete(retrievedComplexRef));
    }

    @Test
    public void testInvalidStoreAsAnnotation() throws StorageException {
        InvalidStoreAsEntity ref = new InvalidStoreAsEntity();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());
        ref.setStoreAsString("StoreAsOverride Data");
        ref.populateEntity();

        this.table.execute(TableOperation.insert(ref));

        TableResult res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(),
                InvalidStoreAsEntity.class));

        InvalidStoreAsEntity retrievedStoreAsRef = res.getResultAsType();
        assertEquals(retrievedStoreAsRef.getStoreAsString(), null);
    }

    @Test
    public void testComplexEntityInsert() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testComplexEntityInsert(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testComplexEntityInsert(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testComplexEntityInsert(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testComplexEntityInsert(options, true);
    }

    private void testComplexEntityInsert(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        ComplexEntity ref = new ComplexEntity();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());
        ref.populateEntity();

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class), options, null);

        ComplexEntity retrievedComplexRef = res.getResultAsType();
        ref.assertEquality(retrievedComplexRef);
    }

    @Test
    public void testDoubles() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testDoubles(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testDoubles(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testDoubles(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testDoubles(options, true);
    }

    private void testDoubles(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        StrangeDoubles ref = new StrangeDoubles();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());
        ref.populateEntity();

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        // try with pojo
        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), StrangeDoubles.class), options, null);

        StrangeDoubles retrievedComplexRef = res.getResultAsType();
        ref.assertEquality(retrievedComplexRef);
    }

    @Test
    public void testWhitespaceTest() throws StorageException  {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testWhitespaceTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testWhitespaceTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testWhitespaceTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testWhitespaceTest(options, true);
    }

    private void testWhitespaceTest(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        Class1 ref = new Class1();

        ref.setA("B    ");
        ref.setB("    A   ");
        ref.setC(" ");
        ref.setD(new byte[] { 0, 1, 2 });
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        assertEquals(((Class1) res.getResult()).getA(), ref.getA());
    }

    @Test
    public void testWhitespaceOnEmptyKeysTest() throws StorageException{
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testWhitespaceOnEmptyKeysTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testWhitespaceOnEmptyKeysTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testWhitespaceOnEmptyKeysTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testWhitespaceOnEmptyKeysTest(options, true);
    }

    private void testWhitespaceOnEmptyKeysTest(TableRequestOptions options, boolean usePropertyResolver)
            throws StorageException {
        Class1 ref = new Class1();

        ref.setA("B    ");
        ref.setB("    A   ");
        ref.setC(" ");
        ref.setD(new byte[] { 0, 1, 2 });
        ref.setPartitionKey("");
        ref.setRowKey("");

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        assertEquals(((Class1) res.getResult()).getA(), ref.getA());

        this.table.execute(TableOperation.delete(ref), options, null);
    }

    @Test
    public void testNewLineTest() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testNewLineTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testNewLineTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testNewLineTest(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testNewLineTest(options, true);
    }

    private void testNewLineTest(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        Class1 ref = new Class1();

        ref.setA("B    ");
        ref.setB("    A   ");
        ref.setC("\r\n");
        ref.setD(new byte[] { 0, 1, 2 });
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        this.table.execute(TableOperation.insert(ref), options, null);

        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), Class1.class), options, null);

        assertEquals(((Class1) res.getResult()).getA(), ref.getA());
    }

    @Test
    public void testNulls() throws StorageException {
        TableRequestOptions options = new TableRequestOptions();

        options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata);
        testNulls(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.Json);
        testNulls(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testNulls(options, false);

        options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        testNulls(options, true);
    }

    private void testNulls(TableRequestOptions options, boolean usePropertyResolver) throws StorageException {
        ComplexEntity ref = new ComplexEntity();
        ref.setPartitionKey("jxscl_odata");
        ref.setRowKey(UUID.randomUUID().toString());
        ref.populateEntity();

        if (usePropertyResolver) {
            options.setPropertyResolver(ref);
        }

        // Binary object
        ref.setBinary(null);

        this.table.execute(TableOperation.insert(ref));
        TableResult res = this.table.execute(
                TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class), options, null);
        ref = res.getResultAsType();

        assertNull("Binary should be null", ref.getBinary());

        // Bool
        ref.setBool(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("Bool should be null", ref.getBool());

        // Date
        ref.setDateTime(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("Date should be null", ref.getDateTime());

        // Double
        ref.setDouble(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("Double should be null", ref.getDouble());

        // UUID
        ref.setGuid(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("UUID should be null", ref.getGuid());

        // Int32
        ref.setInt32(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("Int32 should be null", ref.getInt32());

        // Int64
        ref.setInt64(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("Int64 should be null", ref.getInt64());

        // String
        ref.setString(null);
        this.table.execute(TableOperation.replace(ref), options, null);

        res = this.table.execute(TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class),
                options, null);

        ref = res.getResultAsType();

        assertNull("String should be null", ref.getString());
    }
} @Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class TableTests {

    /**
     * Tests name validation of tables.
     */
    @Test
    public void testCloudTableNameValidation() {
        NameValidator.validateTableName("alpha");
        NameValidator.validateTableName("alphanum3r1c");
        NameValidator.validateTableName("CapsLock");
        NameValidator.validateTableName("$MetricsTransactionsBlob");
        NameValidator.validateTableName("$MetricsHourPrimaryTransactionsTable");
        NameValidator.validateTableName("$MetricsMinuteSecondaryTransactionsQueue");
        NameValidator.validateTableName("tables");
        NameValidator.validateTableName("$MetricsCapacityBlob");

        invalidTableTestHelper(null, "Null not allowed.", "Invalid table name. The name may not be null, empty, or whitespace only.");
        invalidTableTestHelper("1numberstart", "Must start with a letter.", "Invalid table name. Check MSDN for more information about valid naming.");
        invalidTableTestHelper("middle-dash", "Alphanumeric only.", "Invalid table name. Check MSDN for more information about valid naming.");
        invalidTableTestHelper("illegal$char", "Alphanumeric only.", "Invalid table name. Check MSDN for more information about valid naming.");
        invalidTableTestHelper("illegal!char", "Alphanumeric only.", "Invalid table name. Check MSDN for more information about valid naming.");
        invalidTableTestHelper("white space", "Alphanumeric only.", "Invalid table name. Check MSDN for more information about valid naming.");
        invalidTableTestHelper("cc", "Between 3 and 63 characters.", "Invalid table name length. The name must be between 3 and 63 characters long.");
        invalidTableTestHelper(new String(new char[64]).replace("\0", "n"), "Between 3 and 63 characters.", "Invalid table name length. The name must be between 3 and 63 characters long.");
    }

    private void invalidTableTestHelper(String tableName, String failMessage, String exceptionMessage) {
        try {
            NameValidator.validateTableName(tableName);
            fail(failMessage);
        } catch (IllegalArgumentException e) {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    @Test
    public void testIsUsePathStyleUri() throws InvalidKeyException, URISyntaxException, StorageException {
        // normal account
        StorageCredentials creds = new StorageCredentialsAccountAndKey("testAccountName",
                "/g6UPBuy0ypCpAbYTL6/KA+dI/7gyoWvLFYmah3IviUP1jykOHHOlA==");
        CloudStorageAccount account = new CloudStorageAccount(creds);
        testIsUsePathStyleUri(creds, account.getTableEndpoint().toString(), false);

        // normal account with path
        creds = new StorageCredentialsAccountAndKey("testAccountName",
                "/g6UPBuy0ypCpAbYTL6/KA+dI/7gyoWvLFYmah3IviUP1jykOHHOlA==");
        account = new CloudStorageAccount(creds);
        testIsUsePathStyleUri(creds, account.getTableEndpoint().toString() + "/mytable", false);

        // custom endpoint
        creds = new StorageCredentialsAccountAndKey("testAccountName",
                "/g6UPBuy0ypCpAbYTL6/KA+dI/7gyoWvLFYmah3IviUP1jykOHHOlA==");
        testIsUsePathStyleUri(creds, "http://www.contoso.com", false);

        // dev store
        account = CloudStorageAccount.getDevelopmentStorageAccount();
        testIsUsePathStyleUri(account.getCredentials(), account.getTableEndpoint().toString(), true);

        // dev store with proxy
        account = CloudStorageAccount.getDevelopmentStorageAccount(new URI("http://ipv4.fiddler"));
        testIsUsePathStyleUri(account.getCredentials(), account.getTableEndpoint().toString(), true);

        // custom endpoints ipv4 with path-style (internal test)
        creds = new StorageCredentialsAccountAndKey("testAccountName",
                "/g6UPBuy0ypCpAbYTL6/KA+dI/7gyoWvLFYmah3IviUP1jykOHHOlA==");
        testIsUsePathStyleUri(creds, "http://93.184.216.119/testAccountName", true);
    }

    private static void testIsUsePathStyleUri(StorageCredentials creds, String tableEndpoint, boolean usePathStyleUris)
            throws URISyntaxException, InvalidKeyException, StorageException {
        CloudTableClient tableClient = new CloudTableClient(new URI(tableEndpoint), creds);
        assertEquals(usePathStyleUris, tableClient.isUsePathStyleUris());

        CloudTable table = tableClient.getTableReference("mytable");
        assertEquals(tableEndpoint + "/mytable", table.getUri().toString());

        String sasToken = table.generateSharedAccessSignature(null, "fakeIdentifier", null, null, null, null);
        tableClient = new CloudTableClient(new URI(tableEndpoint),
                new StorageCredentialsSharedAccessSignature(sasToken));
        assertEquals(usePathStyleUris, tableClient.isUsePathStyleUris());

        table = new CloudTable(table.getUri(), tableClient.getCredentials());
        assertEquals(tableEndpoint + "/mytable", table.getUri().toString());
    }

    /**
     * Get permissions from string
     */
    @Test
    public void testTablePermissionsFromString() {
        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        Date start = cal.getTime();
        cal.add(Calendar.MINUTE, 30);
        Date expiry = cal.getTime();

        SharedAccessTablePolicy policy = new SharedAccessTablePolicy();
        policy.setSharedAccessStartTime(start);
        policy.setSharedAccessExpiryTime(expiry);

        policy.setPermissionsFromString("raud");
        assertEquals(EnumSet.of(SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.ADD,
                SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE), policy.getPermissions());

        policy.setPermissionsFromString("rad");
        assertEquals(EnumSet.of(SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.ADD,
                SharedAccessTablePermissions.DELETE), policy.getPermissions());

        policy.setPermissionsFromString("ar");
        assertEquals(EnumSet.of(SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.ADD),
                policy.getPermissions());

        policy.setPermissionsFromString("u");
        assertEquals(EnumSet.of(SharedAccessTablePermissions.UPDATE), policy.getPermissions());
    }

    @Test
    public void testTableCreateAndAttemptCreateOnceExists() throws StorageException, URISyntaxException {

        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);
        tClient.getDefaultRequestOptions().setTablePayloadFormat(TablePayloadFormat.Json);
        try {
            table.create();
            assertTrue(table.exists());

            // Should fail as it already exists
            try {
                table.create();
                fail();
            } catch (StorageException ex) {
                assertEquals(ex.getErrorCode(), "TableAlreadyExists");
            }
        } finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableCreateExistsAndDelete() throws StorageException, URISyntaxException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);
        try {
            assertTrue(table.createIfNotExists());
            assertTrue(table.exists());
            assertTrue(table.deleteIfExists());
        } finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableCreateIfNotExists() throws StorageException, URISyntaxException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);
        try {
            assertTrue(table.createIfNotExists());
            assertTrue(table.exists());
            assertFalse(table.createIfNotExists());
        } finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableDeleteIfExists() throws StorageException, URISyntaxException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);
        assertFalse(table.deleteIfExists());

        table.create();
        assertTrue(table.exists());
        assertTrue(table.deleteIfExists());

        assertFalse(table.deleteIfExists());
    }

    @Test
    public void testCloudTableDeleteIfExistsErrorCode() throws StorageException, URISyntaxException {
        final CloudTable table = TableTestHelper.getRandomTableReference();

        try {
            assertFalse(table.deleteIfExists());
            OperationContext ctx = new OperationContext();
            ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

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

            table.create();

            // The second delete of a table will return a 404
            assertFalse(table.deleteIfExists(null, ctx));
        } finally {
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableDeleteWhenExistAndNotExists() throws StorageException, URISyntaxException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);

        try {
            // Should fail as it doesnt already exists
            try {
                table.delete();
                fail();
            } catch (StorageException ex) {
                assertEquals(ex.getMessage(), "Not Found");
            }

            table.create();
            assertTrue(table.exists());
            table.delete();
            assertFalse(table.exists());
        } finally {
            table.deleteIfExists();
        }
    }

    @Test
    public void testTableDoesTableExist() throws StorageException, URISyntaxException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);

        try {
            assertFalse(table.exists());
            assertTrue(table.createIfNotExists());
            assertTrue(table.exists());
        } finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testTableGetSetPermissionTest() throws StorageException, URISyntaxException, InterruptedException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();
        String tableName = TableTestHelper.generateRandomTableName();

        CloudTable table = tClient.getTableReference(tableName);
        table.create();

        TablePermissions expectedPermissions;
        TablePermissions testPermissions;

        try {
            // Test new permissions.
            expectedPermissions = new TablePermissions();
            testPermissions = table.downloadPermissions();
            assertTablePermissionsEqual(expectedPermissions, testPermissions);

            // Test setting empty permissions.
            table.uploadPermissions(expectedPermissions);
            Thread.sleep(30000);

            testPermissions = table.downloadPermissions();
            assertTablePermissionsEqual(expectedPermissions, testPermissions);

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

            policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY,
                    SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE));
            expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1);

            table.uploadPermissions(expectedPermissions);
            Thread.sleep(30000);
            testPermissions = table.downloadPermissions();
            assertTablePermissionsEqual(expectedPermissions, testPermissions);
        } finally {
            // cleanup
            table.deleteIfExists();
        }
    }

    @Test
    @Category(SlowTests.class)
    public void testTableSas() throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException {
        CloudTableClient tClient = TableTestHelper.createCloudTableClient();

        // use capital letters to make sure SAS signature converts name to lower case correctly
        String name = "CAPS" + TableTestHelper.generateRandomTableName();
        CloudTable table = tClient.getTableReference(name);
        table.create();

        TablePermissions expectedPermissions = new TablePermissions();
        String identifier = UUID.randomUUID().toString();
        // Add a policy, check setting and getting.
        SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy();
        Calendar now = GregorianCalendar.getInstance();
        now.add(Calendar.MINUTE, -10);
        policy1.setSharedAccessStartTime(now.getTime());
        now.add(Calendar.MINUTE, 30);
        policy1.setSharedAccessExpiryTime(now.getTime());

        policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY,
                SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE));
        expectedPermissions.getSharedAccessPolicies().put(identifier, policy1);

        table.uploadPermissions(expectedPermissions);
        Thread.sleep(30000);

        // Insert 500 entities in Batches to query
        for (int i = 0; i < 5; i++) {
            TableBatchOperation batch = new TableBatchOperation();

            for (int j = 0; j < 100; j++) {
                Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i));
                ent.setRowKey(String.format("%06d", j));
                batch.insert(ent);
            }

            table.execute(batch);
        }

        String sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", "0",
                "javatables_batch_9", "9");
        CloudTableClient tableClientFromPermission = new CloudTableClient(tClient.getEndpoint(),
                new StorageCredentialsSharedAccessSignature(sasString));

        CloudTable policySasTable = tableClientFromPermission.getTableReference(name);

        // do not give the client and check that the new table's client has the correct perms
        CloudTable tableFromUri = new CloudTable(PathUtility.addToQuery(table.getStorageUri(), table
                .generateSharedAccessSignature((SharedAccessTablePolicy) null, identifier, "javatables_batch_0", "0",
                        "javatables_batch_9", "9")));
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), tableFromUri.getServiceClient()
                .getCredentials().getClass().toString());

        // create credentials from sas
        StorageCredentials creds = new StorageCredentialsSharedAccessSignature(
                table.generateSharedAccessSignature((SharedAccessTablePolicy) null, identifier, "javatables_batch_0", "0",
                        "javatables_batch_9", "9"));
        CloudTableClient tableClient = new CloudTableClient(policySasTable.getServiceClient().getStorageUri(), creds);

        // set some arbitrary settings to make sure they are passed on
        tableClient.getDefaultRequestOptions().setLocationMode(LocationMode.PRIMARY_THEN_SECONDARY);
        tableClient.getDefaultRequestOptions().setTimeoutIntervalInMs(1000);
        tableClient.getDefaultRequestOptions().setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);
        tableClient.getDefaultRequestOptions().setRetryPolicyFactory(new RetryNoRetry());

        tableFromUri = tableClient.getTableReference(table.getName());
        assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), tableFromUri.getServiceClient()
                .getCredentials().getClass().toString());

        assertEquals(tableClient.getDefaultRequestOptions().getLocationMode(), tableFromUri.getServiceClient()
                .getDefaultRequestOptions().getLocationMode());
        assertEquals(tableClient.getDefaultRequestOptions().getTimeoutIntervalInMs(), tableFromUri.getServiceClient()
                .getDefaultRequestOptions().getTimeoutIntervalInMs());
        assertEquals(tableClient.getDefaultRequestOptions().getTablePayloadFormat(), tableFromUri.getServiceClient()
                .getDefaultRequestOptions().getTablePayloadFormat());
        assertEquals(tableClient.getDefaultRequestOptions().getRetryPolicyFactory().getClass(), tableFromUri
                .getServiceClient().getDefaultRequestOptions().getRetryPolicyFactory().getClass());
    }

    private static void assertTablePermissionsEqual(TablePermissions expected, TablePermissions actual) {
        HashMap<String, SharedAccessTablePolicy> expectedPolicies = expected.getSharedAccessPolicies();
        HashMap<String, SharedAccessTablePolicy> actualPolicies = actual.getSharedAccessPolicies();
        assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size());
        for (String name : expectedPolicies.keySet()) {
            assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name));
            SharedAccessTablePolicy expectedPolicy = expectedPolicies.get(name);
            SharedAccessTablePolicy 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());

        }

    }
}
import com.microsoft.azure.storage.analytics.CloudAnalyticsClientTests;
import com.microsoft.azure.storage.blob.BlobOutputStreamTests;
import com.microsoft.azure.storage.blob.CloudAppendBlobTests;
import com.microsoft.azure.storage.blob.CloudBlobClientTests;
import com.microsoft.azure.storage.blob.CloudBlobContainerTests;
import com.microsoft.azure.storage.blob.CloudBlobDirectoryTests;
import com.microsoft.azure.storage.blob.CloudBlobServerEncryptionTests;
import com.microsoft.azure.storage.blob.CloudBlockBlobTests;
import com.microsoft.azure.storage.blob.CloudPageBlobTests;
import com.microsoft.azure.storage.blob.LeaseTests;
import com.microsoft.azure.storage.blob.SasTests;
import com.microsoft.azure.storage.file.CloudFileClientTests;
import com.microsoft.azure.storage.file.CloudFileDirectoryTests;
import com.microsoft.azure.storage.file.CloudFileShareTests;
import com.microsoft.azure.storage.file.CloudFileTests;
import com.microsoft.azure.storage.file.FileSasTests;
import com.microsoft.azure.storage.queue.CloudQueueClientGB18030Test;
import com.microsoft.azure.storage.queue.CloudQueueClientTests;
import com.microsoft.azure.storage.queue.CloudQueueTests;
import com.microsoft.azure.storage.table.TableBatchOperationTests;
import com.microsoft.azure.storage.table.TableClientTests;
import com.microsoft.azure.storage.table.TableDateTests;
import com.microsoft.azure.storage.table.TableEscapingTests;
import com.microsoft.azure.storage.table.TableODataTests;
import com.microsoft.azure.storage.table.TableOperationTests;
import com.microsoft.azure.storage.table.TableQueryTests;
import com.microsoft.azure.storage.table.TableSerializerTests;
import com.microsoft.azure.storage.table.TableTests;
import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.ExcludeCategory;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
 * Copyright Microsoft Corporation
 * <p/>
 * 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
 * <p/>
 * 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;

//import com.microsoft.azure.storage.analytics.CloudAnalyticsClientTests;



/**
 * Contains the various test suites and test categories used to run JUnit tests
 *
 * To run with junit from command line:
 * 1. Install junit and add it to the classpath.
 * 2. Add any project dependencies to the classpath. (ex CLASSPATH=%CLASSPATH%;{path to dependency};)
 * 3. If you have the compiled source jar and compiled test jar, add them to the classpath. If you do not, either run
 * mvn jar:jar and mvn jar:test-jar which will add a compiled source and compiled test jar to the target directory or
 * run mvn install -DskipTests=true which will add those jars to both the target directory and your local repository
 * directory.
 * 4. Suite: java org.junit.runner.JUnitCore com.microsoft.azure.storage.TestRunners$FastTestSuite
 * 5. Class: java org.junit.runner.JUnitCore com.microsoft.azure.storage.TableOperationTests
 * 6. Test: can't do this natively; must make a runner
 *
 * To run with maven from command line:
 * 1. Suite: mvn test -DrunSuite="TestRunners.FastTestSuite"
 * 2. Class: mvn test -Dtest=TableOperationTests
 * 3. Test: mvn test -Dtest=TableOperationTests$testDelete
 *
 * To run with eclipse:
 * 1. Right click the suite/class/test you'd like to run in the package explorer and click Run As > JUnit Test
 *
 * Other notes about suites:
 * 1. Can't include or exclude multipe categories, instead make two suites and merge them into one.
 * 2. With maven you cannot (easily) run tests from inside a jar.
 * 3. To make new suites:
 * Make a new public static class with the name of the suite
 * Specify which test classes to look at (ex, core, blob, queue, table or all) with @SuiteClasses
 * Optional: Specify which category (only one!) to include within those test classes with @IncludeCategory
 * Optional: Specify which category (only one!) to ignore within those test classes with @ExcludeCategory
 * If using categories, specify @RunWith(Categories.class), otherwise @RunWith(Suite.class)
 * 4. To make new categories:
 * Make a public interface with the category name you'd like to use
 * Annotate test classes AND/OR test methods with that category using @Category(categoryName.class)
 * Follow the instructions right above in 3 to use these categories with a suite
 */
public class TestRunners {

    // Speed test category
    public interface SlowTests {
    }

    // Tenant type test categories
    public interface DevStoreTests {
    }

    public interface CloudTests {
    }

    public interface DevFabricTests {
    }

    public interface PremiumBlobTests {
    }

    // Test suites
    @RunWith(Suite.class)
    @SuiteClasses({AccountSasTests.class, EventFiringTests.class, GenericTests.class, MaximumExecutionTimeTests.class,
            SecondaryTests.class, ServicePropertiesTests.class, StorageAccountTests.class,
            StorageUriTests.class})
    public static class CoreTestSuite {
    }

    @RunWith(Suite.class)
    @SuiteClasses({BlobOutputStreamTests.class, CloudBlobClientTests.class, CloudBlobContainerTests.class,
            CloudBlobDirectoryTests.class, CloudAppendBlobTests.class, CloudBlockBlobTests.class, CloudPageBlobTests.class,