UNPKG

azure-storage

Version:

Microsoft Azure Storage Client Library for Node.js

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

var assert = require('assert');
var util = require('util');
var crypto = require('crypto');

var fs = require('fs');
var path = require('path');
// Test includes
var testutil = require('../../framework/util');
var TestSuite = require('../../framework/test-suite');

// Lib includes
var azureutil = testutil.libRequire('/common/util/util');
var azure = testutil.libRequire('azure-storage');
var rfs = testutil.libRequire('common/streams/readablefs');
var SR = azure.SR;
var Constants = azure.Constants;
var HttpConstants = Constants.HttpConstants;
var HeaderConstants = Constants.HeaderConstants;
var BlobUtilities = azure.BlobUtilities;

var blobService;
var containerNames = [];
var containerNamesPrefix = 'cont';
var blobNames = [];
var blobNamesPrefix = 'blob';
var containerNamePrefix = 'upload-download-test';
var blockIdPrefix = 'block';
var containerCount = 0;
var containerName = '';

var blockBlobName = 'blockblob_nodesdktest';
var pageBlobName = 'pageblob_nodesdktest';
var appendBlobName = 'appendblob_nodesdktest';

var blockFileName = 'blobservice_test_block.tmp';
var pageFileName = 'blobservice_test_page.tmp';
var page2KFileName = 'blobservice_test_2K_page.tmp';
var appendFileName = 'blobservice_test_append.tmp';
var append2KFileName = 'blobservice_test_2K_append.tmp';
var notExistFileName = 'blobservice_not_exist.tmp';
var zeroSizeFileName = 'blobservice_zero_size_file.tmp';
var downloadName = 'blobservice_download.tmp';

var fileText = 'Hello World!';

var pageBlobBuffer = new Buffer(1 * 1024);
var pageBlob2KBuffer = new Buffer(2 * 1024);
var appendBlobBuffer = new Buffer(1 * 1024);
var appendBlob2KBuffer = new Buffer(2 * 1024);

var pageBlobContentMD5 = '';
var pageBlob2KContentMD5 = '';
var blockBlobContentMD5 = null;
var appendBlobContentMD5 = '';
var appendBlob2KContentMD5 = '';

var uploadOptions = {
  blockIdPrefix: blockIdPrefix
};

var suite = new TestSuite('blobservice-uploaddownload-tests');
var runOrSkip = suite.isMocked ? it.skip : it;

function getContentMD5(content) {
  var md5hash = crypto.createHash('md5');
  md5hash.update(content);
  return md5hash.digest('base64');
}

function writeFile(fileName, content) {
  fs.writeFileSync(fileName, content);
  return getContentMD5(content);
}

var generateTempFile = function (fileName, size, hasEmptyBlock, callback) {
  var blockSize = 4 * 1024 * 1024;
  var fileInfo = { name: fileName, contentMD5: '', size: size, content: '' };

  var md5hash = crypto.createHash('md5');
  var offset = 0;
  var file = fs.openSync(fileName, 'w');
  var saveContent = size <= blockSize;

  do {
    var value = crypto.randomBytes(1);
    var zero = hasEmptyBlock ? (parseInt(value[0], 10) >= 64) : false;
    var writeSize = Math.min(blockSize, size);
    var buffer;

    if (zero) {
      buffer = new Buffer(writeSize);
      buffer.fill(0);
    } else {
      buffer = crypto.randomBytes(writeSize);
    }

    fs.writeSync(file, buffer, 0, buffer.length, offset);
    size -= buffer.length;
    offset += buffer.length;
    md5hash.update(buffer);

    if (saveContent) {
      fileInfo.content += buffer.toString();
    }
  } while (size > 0);

  fileInfo.contentMD5 = md5hash.digest('base64');
  callback(fileInfo);
};

var getFileMD5 = function (fileName, callback) {
  var md5hash = crypto.createHash('md5');
  var blockSize = 4 * 1024 * 1024;
  var buffer = new Buffer(blockSize);
  var offset = 0;
  var bytesRead = 0;
  var file = fs.openSync(fileName, 'r');
  var fileInfo = { name: fileName, contentMD5: '' };
  do {
    bytesRead = fs.readSync(file, buffer, 0, buffer.length, offset);
    if (bytesRead > 0) {
      offset += bytesRead;
      if (blockSize == bytesRead) {
        md5hash.update(buffer);
      } else {
        md5hash.update(buffer.slice(0, bytesRead));
      }
    }
  } while (bytesRead > 0);

  fileInfo.contentMD5 = md5hash.digest('base64');
  callback(fileInfo);
};

var assertNoStalePropertyOnBlob = function (blob) {
  assert.notEqual(blob, null);
  assert.strictEqual(blob.leaseStatus, undefined);
  assert.strictEqual(blob.leaseId, undefined);
  assert.strictEqual(blob.leaseDuration, undefined);
  assert.strictEqual(blob.leaseState, undefined);

  assert.strictEqual(blob.copySource, undefined);
  assert.strictEqual(blob.copyStatus, undefined);
  assert.strictEqual(blob.copyCompletionTime, undefined);
  assert.strictEqual(blob.copyStatusDescription, undefined);
  assert.strictEqual(blob.copyId, undefined);
  assert.strictEqual(blob.copyProgress, undefined);
};

describe('blob-uploaddownload-tests', function () {
  before(function (done) {
    if (suite.isMocked) {
      testutil.POLL_REQUEST_INTERVAL = 0;
    }
    suite.setupSuite(function () {
      blobService = azure.createBlobService().withFilter(new azure.ExponentialRetryPolicyFilter());
      //blobService.logger.level = azure.Logger.LogLevels.DEBUG;
      done();
    });
  });

  after(function (done) {
    try { fs.unlinkSync(blockFileName); } catch (e) { }
    try { fs.unlinkSync(pageFileName); } catch (e) { }
    try { fs.unlinkSync(page2KFileName); } catch (e) { }
    try { fs.unlinkSync(appendFileName); } catch (e) { }
    try { fs.unlinkSync(append2KFileName); } catch (e) { }
    try { fs.unlinkSync(notExistFileName); } catch (e) { }
    try { fs.unlinkSync(zeroSizeFileName); } catch (e) { }
    try { fs.unlinkSync(downloadName); } catch (e) { }
    suite.teardownSuite(done);
  });

  beforeEach(function (done) {
    containerName = testutil.generateId(containerNamesPrefix, containerNames, suite.isMocked);
    suite.setupTest(function () {
      blobService.createContainerIfNotExists(containerName, function (error) {
        assert.equal(error, null);
        done();
      });
    });
  });

  afterEach(function (done) {
    blobService.deleteContainerIfExists(containerName, function (error) {
      assert.equal(error, null);
      suite.teardownTest(done);
    });
  });

  it('CreateBlobWithBars', function (done) {
    var blobName = 'blobs/' + testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
    var blobText = 'Hello World!';

    // Create the empty page blob
    blobService.createBlockBlobFromText(containerName, blobName, blobText, function (err) {
      assert.equal(err, null);

      blobService.getBlobProperties(containerName, blobName, function (error, properties) {
        assert.equal(error, null);
        assert.equal(properties.container, containerName);
        assert.equal(properties.blob, blobName);

        done();
      });
    });
  });
  
  // This test ensures that blocks can be created from files correctly
  // and was created to ensure that the request module does not magically add
  // a content type to the request when the user did not specify one.
  it('works with files without specifying content type', function (done) {
    fs.writeFileSync(blockFileName, fileText);

    var callback = function (webresource) {
      assert.notEqual(webresource.headers[HeaderConstants.CONTENT_MD5], null);
    };

    blobService.on('sendingRequestEvent', callback);

    blobService.createBlockFromStream('test', containerName, blockBlobName, rfs.createReadStream(blockFileName), fileText.length, {useTransactionalMD5: true}, function (error) {
      assert.equal(error, null);
      blobService.removeAllListeners('sendingRequestEvent');

      done();
    });
  });
  
  describe('blob-piping-tests', function() {
    runOrSkip('should be able to upload block blob from piped stream', function (done) { 
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('uploadBlockBlobPiping', [], suite.isMocked) + '.blocktest';
      var blobBuffer = new Buffer( 6 * 1024 * 1024);
      blobBuffer.fill(1);

      // Write file so that it can be piped
      fs.writeFileSync(fileNameTarget, blobBuffer);
      
      // Pipe file to a blob
      var stream = rfs.createReadStream(fileNameTarget).pipe(blobService.createWriteStreamToBlockBlob(containerName, blobName, { blockIdPrefix: 'block' }));
      stream.on('close', function () {
        blobService.getBlobToText(containerName, blobName, function (err, text) {
          assert.equal(err, null);

          assert.equal(text, blobBuffer);

          try { fs.unlinkSync(fileNameTarget); } catch (e) {}

          done();
        });
      });
    });
  
    runOrSkip('should be able to upload pageblob from piped stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('uploadPageBlobPiping', [], suite.isMocked) + '.test';
      var blobBuffer = new Buffer( 5 * 1024 * 1024 );
      blobBuffer.fill(1);

      // Write file so that it can be piped
      fs.writeFile(fileNameTarget, blobBuffer, function() {
        blobService.createPageBlob(containerName, blobName, 5 * 1024 * 1024, function (err) {
          assert.equal(err, null);
          // Pipe file to a blob
          var stream = blobService.createWriteStreamToExistingPageBlob(containerName, blobName);
          var readable = rfs.createReadStream(fileNameTarget);
          readable.pipe(stream);
          stream.on('close', function () {
            blobService.getBlobToText(containerName, blobName, function (err, text) {
              assert.equal(err, null);

              assert.equal(text, blobBuffer);

              try { fs.unlinkSync(fileNameTarget); } catch (e) {}
              done();
            });
          });
        });
      });
    });

    runOrSkip('should be able to upload a pageblob from piped stream and store the MD5 on the server', function (done) {
      containerName = testutil.generateId(containerNamesPrefix, containerNames, suite.isMocked);
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('uploadPageBlobMD5Piping', [], suite.isMocked) + '.test';
      var length = 3 * 1024 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill(1);

      blobService.createContainer(containerName, function (createError1, container1) {
        assert.equal(createError1, null);
        assert.notEqual(container1, null);

        // Write file so that it can be piped
        pageBlobContentMD5 = writeFile(fileNameTarget, blobBuffer);

        // Pipe file to a blob
        var stream = blobService.createWriteStreamToNewPageBlob(containerName, blobName, length, {storeBlobContentMD5: true});
        var readable = rfs.createReadStream(fileNameTarget);
        readable.pipe(stream);
        stream.on('close', function () {
          blobService.getBlobProperties(containerName, blobName, function (err, blob) {
            assert.equal(err, null);
            assert.equal(blob.contentMD5, pageBlobContentMD5);

            try { fs.unlinkSync(fileNameTarget); } catch (e) {}
            done();
          });
        });
      });
    });

    runOrSkip('should be able to upload an appendblob from piped stream', function (done) {
      containerName = testutil.generateId(containerNamesPrefix, containerNames, suite.isMocked);
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('uploadAppendBlobMD5Piping', [], suite.isMocked) + '.test';
      var length = 3 * 1024 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill(1);

      // Write file so that it can be piped
      writeFile(fileNameTarget, blobBuffer);
      
      blobService.createContainer(containerName, function (createError1, container1) {
        assert.equal(createError1, null);
        assert.notEqual(container1, null);
        
        blobService.createOrReplaceAppendBlob(containerName, blobName, length, function (err) {
          assert.equal(err, null);
          
          assert.throws(function () { 
            blobService.createWriteStreamToExistingAppendBlob(containerName, blobName, { storeBlobContentMD5: true }); 
          });

          // Pipe file to a blob
          var stream = blobService.createWriteStreamToExistingAppendBlob(containerName, blobName, { storeBlobContentMD5: false });
          rfs.createReadStream(fileNameTarget).pipe(stream);

          stream.on('close', function () {
            blobService.getBlobProperties(containerName, blobName, function (err, blob) {
              assert.equal(err, null);
              assert.equal(blob.contentLength, length);
              assert.equal(blob.blobType, 'AppendBlob');
              
              try { fs.unlinkSync(fileNameTarget); } catch (e) { }
              done();
            });
          });
        });
      });
    });

    runOrSkip('should be able to upload new page blob from piped stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('uploadPageBlobStreamPiping', [], suite.isMocked) + '.test';
      var length = 6 * 1024 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill(1);

      // Write file so that it can be piped
      fs.writeFile(fileNameTarget, blobBuffer, function() {
        // Pipe file to a blob
        var stream = blobService.createWriteStreamToNewPageBlob(containerName, blobName, length);
        var readable = rfs.createReadStream(fileNameTarget);
        readable.pipe(stream);
        stream.on('close', function () {
          blobService.getBlobToText(containerName, blobName, function (err, text) {
            assert.equal(err, null);
            assert.equal(text, blobBuffer);
            try { fs.unlinkSync(fileNameTarget); } catch (e) { }
            done();
          });
        });
      });
    });

    runOrSkip('should be able to download blob to piped stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var sourceFileNameTarget = testutil.generateId('getBlobSourceFile', [], suite.isMocked) + '.test';
      var destinationFileNameTarget = testutil.generateId('getBlobDestinationFile', [], suite.isMocked) + '.test';
      var length = 5 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill(1);

      fs.writeFileSync(sourceFileNameTarget, blobBuffer);

      blobService.createPageBlobFromStream(containerName, blobName, rfs.createReadStream(sourceFileNameTarget), length, function(uploadError) {
        assert.equal(uploadError, null);
        var writable = fs.createWriteStream(destinationFileNameTarget);
        blobService.createReadStream(containerName, blobName).pipe(writable);

        writable.on('close', function () {
          var exists = azureutil.pathExistsSync(destinationFileNameTarget);
          assert.equal(exists, true);

          fs.readFile(destinationFileNameTarget, function (err, destFileText) {
            fs.readFile(sourceFileNameTarget, function (err, srcFileText) {
              assert.deepEqual(destFileText, srcFileText);

              try { fs.unlinkSync(sourceFileNameTarget); } catch (e) {}
              try { fs.unlinkSync(destinationFileNameTarget); } catch (e) {}

              done();
            });
          });
        });
      });
    });
    
    it('should emit error events when using piped streams', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('streamErrorPiping', [], suite.isMocked) + '.test';

      var stream = blobService.createReadStream(containerName, blobName);
      stream.on('error', function (error) {
        assert.equal(error.code, 'NotFound');
        assert.equal(error.statusCode, '404');
        assert.notEqual(error.requestId, null);

        try { fs.unlinkSync(fileNameTarget); } catch (e) {}

        done();
      });

      stream.pipe(fs.createWriteStream(fileNameTarget));
    });
  });
  
  describe('blob-rangedownload-tests', function() {
    it('getBlobRange', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var data1 = 'Hello, World!';

      // Create the empty page blob
      blobService.createBlockBlobFromText(containerName, blobName, data1, function (err) {
        assert.equal(err, null);

        blobService.getBlobToText(containerName, blobName, { rangeStart: 2, rangeEnd: 3 }, function (err3, content1) {
          assert.equal(err3, null);

          // get the double ll's in the hello
          assert.equal(content1, 'll');

          done();
        });
      });
    });

    it('getBlobRangeOpenEnded', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var data1 = 'Hello, World!';

      // Create the empty page blob
      blobService.createBlockBlobFromText(containerName, blobName, data1, function (err) {
        assert.equal(err, null);

        blobService.getBlobToText(containerName, blobName, { rangeStart: 2 }, function (err3, content1) {
          assert.equal(err3, null);

          // get the last bytes from the message
          assert.equal(content1, 'llo, World!');

          done();
        });
      });
    });

    it('getPageRanges', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getPageRanges', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(512);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      // Upload contents in 2 parts
      blobService.createPageBlob(containerName, blobName, 1024 * 1024 * 1024, function (err) {
        assert.equal(err, null);

        fs.writeFile(fileNameSource, blobBuffer, function () {

          var callback = function (webresource) {
            assert.notEqual(webresource.headers[HeaderConstants.CONTENT_MD5], null);
          };

          blobService.on('sendingRequestEvent', callback);
          blobService.createPagesFromStream(containerName, blobName, rfs.createReadStream(fileNameSource), 0, 511, {useTransactionalMD5: true}, function(err2) {
            // Upload all data
            assert.equal(err2, null);
            blobService.removeAllListeners('sendingRequestEvent');

            // Only one range present
            blobService.listPageRanges(containerName, blobName, function (error, ranges) {
              assert.equal(error, null);
              assert.notEqual(ranges, null);
              if (ranges) {
                assert.equal(ranges.length, 1);

                var entries = 0;
                ranges.forEach(function (range) {
                  if (range.start === 0) {
                    assert.equal(range.end, 511);
                    entries += 1;
                  }
                });

                assert.equal(entries, 1);
              }

              blobService.on('sendingRequestEvent', callback);

              blobService.createPagesFromStream(containerName, blobName, rfs.createReadStream(fileNameSource), 1048576, 1049087, {contentMD5: azureutil.getContentMd5(blobBuffer)}, function (err3) {
                assert.equal(err3, null);

                blobService.removeAllListeners('sendingRequestEvent');

                // Get page ranges
                blobService.listPageRanges(containerName, blobName, function (error5, ranges) {
                  assert.equal(error5, null);
                  assert.notEqual(ranges, null);
                  if (ranges) {
                    assert.equal(ranges.length, 2);

                    var entries = 0;
                    ranges.forEach(function (range) {
                      if (range.start === 0) {
                        assert.equal(range.end, 511);
                        entries += 1;
                      }
                      else if (range.start === 1048576) {
                        assert.equal(range.end, 1049087);
                        entries += 2;
                      }
                    });

                    assert.equal(entries, 3);
                  }
                  
                  blobService.getBlobToLocalFile(containerName, blobName, fileNameSource, function (error) {
                    assert.equal(error, null);
                    try { fs.unlinkSync(fileNameSource); } catch (e) { }
                    done();
                  });
                });
              });
            });
          });
        });
      });
    });

    runOrSkip('should download a range of block blob to stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlockBlobRangeStreamLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, false, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }
          
          var downloadOptions = { parallelOperationThreadCount : 5, rangeStart: 100, rangeEnd: 70000000 };
          var downloadedFileName = testutil.generateId('getBlockBlobRangeStream', [], suite.isMocked) + '.test';
          var stream = fs.createWriteStream(downloadedFileName);
          blobService.getBlobToStream(containerName, blobName, stream, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });
  });
  
  describe('CreateBlock', function() {
    it('createBlockFromStream should work without useTransactionalMD5', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileName = testutil.generateId('prefix', null, suite.isMocked) + '.txt';
      var blobText = 'Hello World!';
      try { fs.unlinkSync(fileName); } catch (e) {}
      fs.writeFileSync(fileName, blobText);
      var stat = fs.statSync(fileName);

      blobService.createBlockFromStream('blockid1', containerName, blobName, rfs.createReadStream(fileName), stat.size, function (error2) {
        assert.equal(error2, null);
        try { fs.unlinkSync(fileName); } catch (e) {}
        done();
      });
    });

    it('createBlockFromText should work with useTransactionalMD5', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var blobText = 'Hello World!';

      var callback = function (webresource) {
        assert.notEqual(webresource.headers[HeaderConstants.CONTENT_MD5], null);
      };

      blobService.on('sendingRequestEvent', callback);

      blobService.createBlockFromText('blockid1', containerName, blobName, blobText, {useTransactionalMD5: true}, function (error2) {
        assert.equal(error2, null);
        blobService.removeAllListeners('sendingRequestEvent');

        done();
      });
    });

    it('CommitBlockList', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);

      blobService.createBlockFromText('id1', containerName, blobName, 'id1', function (error2) {
        assert.equal(error2, null);

        blobService.createBlockFromText('id2', containerName, blobName, 'id2', function (error3) {
          assert.equal(error3, null);

          var blockList = {
            LatestBlocks: ['id1'],
            UncommittedBlocks: ['id2']
          };

          blobService.commitBlocks(containerName, blobName, blockList, function (error4, blob) {
            assert.equal(error4, null);
            assert.equal(blob.container, containerName);
            assert.equal(blob.blob, blobName);
            assert.deepEqual(blob.list, blockList);
            assert.notEqual(blob.etag, null);
            assert.notEqual(blob.lastModified, null);
            assert.notEqual(blob.contentMD5, null);

            blobService.listBlocks(containerName, blobName, BlobUtilities.BlockListFilter.ALL, function (error5, list) {
              assert.equal(error5, null);
              assert.notEqual(list, null);
              assert.notEqual(list.CommittedBlocks, null);
              assert.equal(list.CommittedBlocks.length, 2);
              
              var fileNameSource = testutil.generateId('CommitBlockList', [], suite.isMocked) + '.test';
              blobService.getBlobToLocalFile(containerName, blobName, fileNameSource, function (error) {
                assert.equal(error, null);
                try { fs.unlinkSync(fileNameSource); } catch (e) { }
                done();
              });
            });
          });
        });
      });
    });

    it('should work with a single block', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);

      blobService.createBlockFromText('id1', containerName, blobName, 'id1', function (error2) {
        assert.equal(error2, null);

        blobService.createBlockFromText('id2', containerName, blobName, 'id2', function (error3) {
          assert.equal(error3, null);

          var blockList = {
            LatestBlocks: ['id1'],
          };

          blobService.commitBlocks(containerName, blobName, blockList, function (error4, blob) {
            assert.equal(error4, null);
            assert.equal(blob.container, containerName);
            assert.equal(blob.blob, blobName);
            assert.deepEqual(blob.list, blockList);
            assert.notEqual(blob.etag, null);
            assert.notEqual(blob.lastModified, null);
            assert.notEqual(blob.contentMD5, null);

            blobService.listBlocks(containerName, blobName, BlobUtilities.BlockListFilter.ALL, function (error5, list) {
              assert.equal(error5, null);
              assert.notEqual(list, null);
              assert.notEqual(list.CommittedBlocks, null);
              assert.equal(list.CommittedBlocks.length, 1);

              blobService.createBlockFromText('id3', containerName, blobName, 'id3', function (error6) {
                assert.equal(error6, null);
                blobService.listBlocks(containerName, blobName, BlobUtilities.BlockListFilter.ALL, function (error7, list) {
                  assert.equal(error7, null);
                  assert.notEqual(list, null);
                  assert.notEqual(list.CommittedBlocks, null);
                  assert.notEqual(list.UncommittedBlocks, null);
                  assert.equal(list.CommittedBlocks.length, 1);
                  assert.equal(list.UncommittedBlocks.length, 1);
                  done();
                });
              });
            });
          });
        });
      });
    });
  });
  
  describe('AppendBlock', function() {
    var appendText = 'stringappendtoblob';

    it('should append a block to the append blob from a text', function (done) {
      blobService.createOrReplaceAppendBlob(containerName, appendBlobName, function (err, blob) {
        assert.equal(err, null);
        
        var textMD5 = getContentMD5(appendText);
        var appendOption = {storeBlobContentMD5: true, useTransactionalMD5: true};
        blobService.appendBlockFromText(containerName, appendBlobName, appendText, appendOption, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, textMD5);

          blobService.getBlobToText(containerName, appendBlobName, function (err, blobText) {
            assert.equal(err, null);
            assert.equal(blobText, appendText);
            done();
          });
        });
      });
    });

    it('should append a block to the append blob from a stream', function (done) {
      blobService.createOrReplaceAppendBlob(containerName, appendBlobName, function (err, blob) {
        assert.equal(err, null);

        var streamMD5 = writeFile(appendFileName, appendText);
        var stream = rfs.createReadStream(appendFileName);
        var appendOption = {storeBlobContentMD5: true, useTransactionalMD5: true};
        blobService.appendBlockFromStream(containerName, appendBlobName, stream, appendText.length, appendOption, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, streamMD5);

          blobService.getBlobToText(containerName, appendBlobName, function (err, blobText) {
            assert.equal(err, null);
            assert.equal(blobText, appendText);
            done();
          });
        });
      });
    });

    it('should append a block to the append blob with conditions', function (done) {
      blobService.createOrReplaceAppendBlob(containerName, appendBlobName, function (err, blob) {
        assert.equal(err, null);

        var stream = rfs.createReadStream(appendFileName);
        var appendOption = { appendPosition: 0 };
        blobService.appendBlockFromStream(containerName, appendBlobName, stream, appendText.length, appendOption, function(err, blob) {
          assert.equal(err, null);

          appendOption.appendPosition += appendText.length;
          blobService.appendBlockFromText(containerName, appendBlobName, appendText, appendOption, function(err, blob) {
            assert.equal(err, null);

            appendOption.appendPosition += appendText.length;
            appendOption.maxBlobSize = appendText.length * 2; 
            blobService.appendBlockFromText(containerName, appendBlobName, 'a', appendOption, function(err, blob) {
              var errorKeyword = 'The max blob size condition specified was not met';
              assert.notEqual(err, null);
              assert.equal(err.statusCode, 412);
              assert.notEqual(err.message.indexOf(errorKeyword), -1);

              blobService.getBlobToText(containerName, appendBlobName, function (err, blobText) {
                assert.equal(err, null);
                assert.equal(blobText, appendText + appendText);
                done();
              });
            });
          });
        });
      });
    });    

    it('should throw conditional error when appends a block from a wrong position', function (done) {
      blobService.createOrReplaceAppendBlob(containerName, appendBlobName, function (err, blob) {
        assert.equal(err, null);

        var appendOption = {appendPosition: 1};
        blobService.appendBlockFromText(containerName, appendBlobName, appendText, appendOption, function(err, blob) {
          assert.equal(err.statusCode, 412);
          done();
        });
      });
    });

    it('should throw conditional error when appends a block exceeding the maximun blob size', function (done) {
      blobService.createOrReplaceAppendBlob(containerName, appendBlobName, function (err, blob) {
        assert.equal(err, null);

        var appendOption = {maxBlobSize: appendText.length - 1};
        blobService.appendBlockFromText(containerName, appendBlobName, appendText, appendOption, function(err, blob) {
          var errorKeyword = 'The max blob size condition specified was not met';
          assert.notEqual(err, null);
          assert.equal(err.statusCode, 412);
          assert.notEqual(err.message.indexOf(errorKeyword), -1);
          done();
        });
      });
    });
  });
   
  describe('blob-MD5Validation-tests', function() {
    runOrSkip('Upload/Download with MD5 validation should work', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('MD5Validation', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(5 * 1024 * 1024);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      fs.writeFile(fileNameSource, blobBuffer, function () {

        var blobOptions = { contentType: 'text', blockIdPrefix : 'blockId'};
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, blobOptions, function (uploadError, blobResponse, uploadResponse) {
          assert.equal(uploadError, null);
          assert.notEqual(blobResponse, null);
          assert.ok(uploadResponse.isSuccessful);

          // Set disableContentMD5Validation to false explicitly.
          blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { disableContentMD5Validation: false }, function (downloadErr, downloadResult) {
            assert.equal(downloadErr, null);
            assert.strictEqual(downloadResult.contentMD5, 'ndpxhuSh0PPmMvK74fkYvg==');

            // Don't set disableContentMD5Validation explicitly. Since this is a block blob, the response will still contain the Content-MD5.
            blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task2-download.txt'), function (downloadErr, downloadResult) {
              assert.equal(downloadErr, null);
              assert.strictEqual(downloadResult.contentMD5, 'ndpxhuSh0PPmMvK74fkYvg==');

              blobService.getBlobProperties(containerName, blobName, function (getBlobPropertiesErr, blobGetResponse) {
                assert.equal(getBlobPropertiesErr, null);
                assert.notEqual(blobGetResponse, null);
                if (blobGetResponse) {
                  assert.equal(blobOptions.contentType, blobGetResponse.contentType);
                }

                try { fs.unlinkSync(fileNameSource); } catch (e) {}
                try { fs.unlinkSync('task1-download.txt'); } catch (e) {}
                try { fs.unlinkSync('task2-download.txt'); } catch (e) {}

                done();
              });
            });
          });
        });
      });
    });

    runOrSkip('BlockBlobDownloadWithAndWithoutMD5Validation', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlockBlobFileMD5', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(5 * 1024 * 1024);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      fs.writeFile(fileNameSource, blobBuffer, function () {

        var blobOptions = { contentType: 'text', blockIdPrefix : 'blockId'};
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, blobOptions, function (uploadError, blobResponse, uploadResponse) {
          assert.equal(uploadError, null);
          assert.notEqual(blobResponse, null);
          assert.ok(uploadResponse.isSuccessful);

          blobService.getBlobProperties(containerName, blobName, function (getBlobPropertiesErr) {
            assert.equal(getBlobPropertiesErr, null);

            var properties = {contentMD5: 'MDAwMDAwMDA='};
            blobService.setBlobProperties(containerName, blobName, properties, function (setBlobPropertiesErr) {
              assert.equal(setBlobPropertiesErr, null);

              blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { disableContentMD5Validation: false }, function (downloadErr) {
                assert.notEqual(downloadErr, null);
                assert.equal(downloadErr.message, util.format(SR.HASH_MISMATCH, 'MDAwMDAwMDA=', 'ndpxhuSh0PPmMvK74fkYvg=='));

                blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task2-download.txt'), function (downloadErr2) {
                  assert.notEqual(downloadErr2, null);
                  assert.equal(downloadErr2.message, util.format(SR.HASH_MISMATCH, 'MDAwMDAwMDA=', 'ndpxhuSh0PPmMvK74fkYvg=='));

                  blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task3-download.txt'), { disableContentMD5Validation: true }, function (downloadErr3) {
                    assert.equal(downloadErr3, null);
                    try { fs.unlinkSync(fileNameSource); } catch (e) {}
                    try { fs.unlinkSync('task3-download.txt'); } catch (e) {}

                    done();
                  });
                });
              });
            });
          });
        });
      });
    });
    
    runOrSkip('PageBlobDownloadWithAndWithoutMD5Validation', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getPageBlobMD5', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(5 * 1024 * 1024);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      fs.writeFile(fileNameSource, blobBuffer, function () {
        var blobOptions = { contentType: 'text'};
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, blobOptions, function (uploadError, blobResponse, uploadResponse) {
          assert.equal(uploadError, null);
          assert.notEqual(blobResponse, null);
          assert.ok(uploadResponse.isSuccessful);

          blobService.getBlobProperties(containerName, blobName, function (getBlobPropertiesErr) {
            assert.equal(getBlobPropertiesErr, null);

            var properties = {contentMD5: 'MDAwMDAwMDA='};
            blobService.setBlobProperties(containerName, blobName, properties, function (setBlobPropertiesErr) {
              assert.equal(setBlobPropertiesErr, null);

              blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { disableContentMD5Validation: false }, function (downloadErr) {
                assert.notEqual(downloadErr, null);
                assert.equal(downloadErr.message, util.format(SR.HASH_MISMATCH, 'MDAwMDAwMDA=', 'ndpxhuSh0PPmMvK74fkYvg=='));

                blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task2-download.txt'), function (downloadErr2) {
                  assert.notEqual(downloadErr2, null);
                  assert.equal(downloadErr2.message, util.format(SR.HASH_MISMATCH, 'MDAwMDAwMDA=', 'ndpxhuSh0PPmMvK74fkYvg=='));

                  blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task3-download.txt'), { disableContentMD5Validation: true }, function (downloadErr3) {
                    assert.equal(downloadErr3, null);
                    try { fs.unlinkSync(fileNameSource); } catch (e) {}
                    try { fs.unlinkSync('task1-download.txt'); } catch (e) {}
                    try { fs.unlinkSync('task2-download.txt'); } catch (e) {}
                    try { fs.unlinkSync('task3-download.txt'); } catch (e) {}

                    done();
                  });
                });
              });
            });
          });
        });
      });
    });
    
    runOrSkip('BlockBlobDownloadRangeValidation', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlockBlobRange', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(5 * 1024 * 1024);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      fs.writeFile(fileNameSource, blobBuffer, function () {
        var blobOptions = { contentType: 'text', blockIdPrefix : 'blockId'};
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, blobOptions, function (uploadError, blobResponse, uploadResponse) {
          assert.equal(uploadError, null);
          assert.notEqual(blobResponse, null);
          assert.ok(uploadResponse.isSuccessful);

          blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { rangeStart: 512, rangeEnd: 1023 }, function (downloadErr, downloadResult) {
            assert.equal(downloadErr, null);
            assert.strictEqual(parseInt(downloadResult.contentLength, 10), 512);
            blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { rangeStart: 512, rangeEnd: 1023, useTransactionalMD5: true }, function (downloadErr, downloadResult) {
              assert.equal(downloadErr, null);
              assert.strictEqual(parseInt(downloadResult.contentLength, 10), 512);
              assert.strictEqual(downloadResult.contentMD5, 'v2GerAzfP2jUluqTRBN+iw==');
              try { fs.unlinkSync(fileNameSource); } catch (e) {}

              done();
            });
          });
        });
      });
    });
  
    runOrSkip('PageBlobDownloadRangeValidation', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getPageBlobRange', [], suite.isMocked) + '.test';

      var blobBuffer = new Buffer(5 * 1024 * 1024);
      blobBuffer.fill(0);
      blobBuffer[0] = '1';

      fs.writeFile(fileNameSource, blobBuffer, function () {
        var blobOptions = { contentType: 'text', blockIdPrefix : 'blockId'};
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, blobOptions, function (uploadError, blobResponse, uploadResponse) {
          assert.equal(uploadError, null);
          assert.notEqual(blobResponse, null);
          assert.ok(uploadResponse.isSuccessful);

          blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { rangeStart: 0, rangeEnd: 511, useTransactionalMD5: true }, function (downloadErr, downloadResult) {
            assert.equal(downloadErr, null);
            assert.strictEqual(parseInt(downloadResult.contentLength, 10), 512);
            blobService.getBlobToStream(containerName, blobName, fs.createWriteStream('task1-download.txt'), { rangeStart: 512, rangeEnd: 1023, useTransactionalMD5: true }, function (downloadErr, downloadResult) {
              assert.equal(downloadErr, null);
              assert.strictEqual(parseInt(downloadResult.contentLength, 10), 512);
              assert.strictEqual(downloadResult.contentMD5, 'v2GerAzfP2jUluqTRBN+iw==');

              try { fs.unlinkSync(fileNameSource); } catch (e) {}
              try { fs.unlinkSync('task1-download.txt'); } catch (e) {}

              done();
            });
          });
        });
      });
    });
  
    it('DownloadTextWithMD5Validation', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var data1 = 'Hello, World!';
        
      var callback = function (webresource) {
        assert.notEqual(webresource.headers[HeaderConstants.CONTENT_MD5], null);
      };

      blobService.on('sendingRequestEvent', callback);
      blobService.createBlockBlobFromText(containerName, blobName, data1, {useTransactionalMD5: true}, function (err) {
        assert.equal(err, null);
        blobService.removeAllListeners('sendingRequestEvent');

        blobService.getBlobToText(containerName, blobName, function (err2, content, result) {
          assert.equal(err2, null);
          assert.equal(content, 'Hello, World!');
          assert.equal(result.contentMD5, 'ZajifYh5KDgxtmS9i38K1A==');
          
          done();
        });
      });
    });
  });

  describe('createBlockBlobFromText', function () {
    it('should work for small size from text', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked) + ' a';
      var blobText = 'Hello World';

      blobService.createBlockBlobFromText(containerName, blobName, blobText, function (uploadError, blobResponse, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResponse, null);
        assertNoStalePropertyOnBlob(blobResponse);
        assert.ok(uploadResponse.isSuccessful);

        blobService.getBlobToText(containerName, blobName, function (downloadErr, blobTextResponse) {
          assert.equal(downloadErr, null);
          assert.equal(blobTextResponse, blobText);

          done();
        });
      });
    });

    it('should automatically store md5', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked) + ' a';
      var blobText = 'Hello World';
      var blobMD5 = azureutil.getContentMd5(blobText);

      blobService.createBlockBlobFromText(containerName, blobName, blobText, function (uploadError, blobResponse, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResponse, null);
        assert.ok(uploadResponse.isSuccessful);

        blobService.getBlobProperties(containerName, blobName, function (error4, blobProperties) {
          assert.equal(error4, null);
          assert.notEqual(blobProperties, null);
          assert.equal(blobProperties.contentMD5, blobMD5);

          done();
        });
      });
    });

    it('should work with access condition', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var blobText = 'hello';

      blobService.createBlockBlobFromText(containerName, blobName, blobText, function (error2) {
        assert.equal(error2, null);

        blobService.getBlobProperties(containerName, blobName, function (error4, blobProperties) {
          assert.equal(error4, null);

          var options = { accessConditions: { 'if-none-match': blobProperties.etag} };
          blobService.createBlockBlobFromText(containerName, blobName, blobText, options, function (error3) {
            assert.notEqual(error3, null);
            assert.equal(error3.code, Constants.StorageErrorCodeStrings.CONDITION_NOT_MET);

            done();
          });
        });
      });
    });

    it('should work with storeBlobContentMD5', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked) + ' a';
      var blobText = 'Hello World';
      var blobMD5 = azureutil.getContentMd5(blobText);

      blobService.createBlockBlobFromText(containerName, blobName, blobText, {storeBlobContentMD5: true, contentMD5: blobMD5}, function (uploadError, blob, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blob, null);
        assert.equal(blob.contentMD5, blobMD5);
        assert.ok(uploadResponse.isSuccessful);

        blobService.getBlobToText(containerName, blobName, function (downloadErr, blobTextResponse) {
          assert.equal(downloadErr, null);
          assert.equal(blobTextResponse, blobText);

          done();
        });
      });
    });
  });

  describe('createBlockBlobFromLocalFile', function() {
    var zeroFileContentMD5;
    before(function (done) {
      blockBlobContentMD5 = writeFile(blockFileName, fileText);
      var zeroBuffer = new Buffer(0);
      zeroFileContentMD5 = writeFile(zeroSizeFileName, zeroBuffer);
      done();
    });

    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, blockBlobName, function (err) {
        assert.equal(err, null);
        done();
      });
    });

    it('should work with basic file', function(done) {
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, uploadOptions, function (err, blob) {
        assert.equal(err, null);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, blockBlobName, function (err, blob) {
          assert.equal(blob.contentMD5, blockBlobContentMD5);

          blobService.getBlobToText(containerName, blockBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, fileText);
            done();
          });
        });
      });
    });

    it('should overwrite the existing blob', function(done) {
      blobService.createBlockBlobFromText(containerName, blockBlobName, 'garbage', uploadOptions, function (err) {
        assert.equal(err, null);
        blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, uploadOptions, function (err) {
          assert.equal(err, null);

          blobService.getBlobToText(containerName, blockBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, fileText);
            done();
          });
        });
      });
    });

    it('should work with zero size file', function(done) {
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, zeroSizeFileName, uploadOptions, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, blockBlobName, function(err, blob) {
          assert.equal(blob.contentLength, 0);
          assert.equal(blob.contentMD5, zeroFileContentMD5);
          done();
        });
      });
    });

    it('should work with content type', function (done) {
      var blobOptions = { contentType: 'text' };
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, blobOptions, function (uploadError, blobResponse, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResponse, null);
        assert.ok(uploadResponse.isSuccessful);

        blobService.getBlobProperties(containerName, blockBlobName, function (getBlobPropertiesErr, blobGetResponse) {
          assert.equal(getBlobPropertiesErr, null);
          assert.notEqual(blobGetResponse, null);
          assert.equal(blobOptions.contentType, blobGetResponse.contentType);
          done();
        });
      });
    });

    it('should work with not existing file', function(done) {
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, notExistFileName, uploadOptions, function (err) {
        assert.notEqual(err, null);
        assert.equal(path.basename(err.path), notExistFileName);

        blobService.doesBlobExist(containerName, blockBlobName, function (existsErr, exists) {
          assert.equal(existsErr, null);
          assert.equal(exists, false);
          done();
        });
      });
    });

    it('should work with metadata', function(done) {
      var options = {
        storeBlobContentMD5 : true,
        useTransactionalMD5 : true,
        metadata: { color: 'blue' }
      };
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, zeroSizeFileName, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, blockBlobName, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentLength, 0);
          assert.equal(blob.contentMD5, zeroFileContentMD5);
          assert.notEqual(blob.metadata, null);
          assert.equal(blob.metadata.color, options.metadata.color);
          done();
        });
      });
    });

    runOrSkip('should have same md5 with range-based downloading to local file', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlobRangeMD5', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, false, function (fileInfo) {
        var baseMD5 = fileInfo.contentMD5;
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          
          blobService.getBlobProperties(containerName, blobName, function (error, blobProperties) {
            assert.equal(error, null);
            assert.notEqual(blobProperties, null);
            assert.strictEqual(blobProperties.contentMD5, baseMD5);
            var downloadOptions = { parallelOperationThreadCount : 5 };
            blobService.getBlobToLocalFile(containerName, blobName, fileNameSource, downloadOptions, function (error) {
              assert.equal(error, null);
              
              getFileMD5(fileNameSource, function (fileInfo) {
                assert.strictEqual(fileInfo.contentMD5, baseMD5);
                try { fs.unlinkSync(fileNameSource); } catch (e) { }
                done();
              });
            });
          });
        });
      });
    });

    runOrSkip('getBlobToLocalFile should return the SpeedSummary correctly', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlobToLocalFileSpeedSummary', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, false, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          
          var speedSummary;
          var downloadOptions = { 
            parallelOperationThreadCount : 5
          };
          
          speedSummary = blobService.getBlobToLocalFile(containerName, blobName, fileNameSource, downloadOptions, function (error) {
            assert.equal(speedSummary.getTotalSize(false), fileSize);
            assert.equal(speedSummary.getCompleteSize(false), fileSize);
            assert.equal(speedSummary.getCompletePercent(), '100.0');
            
            try { fs.unlinkSync(fileNameSource); } catch (e) { }
            done();
          });
          
          assert.notEqual(speedSummary, null);
        });
      });
    });
    
    runOrSkip('should have same md5 with range-based downloading to stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlobRangeStreamMD5Local', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, false, function (fileInfo) {
        var baseMD5 = fileInfo.contentMD5;
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          
          blobService.getBlobProperties(containerName, blobName, function (error, blobProperties) {
            assert.equal(error, null);
            assert.notEqual(blobProperties, null);
            assert.strictEqual(blobProperties.contentMD5, baseMD5);
            var downloadOptions = { parallelOperationThreadCount : 5 };
            var downloadedFileName = testutil.generateId('getBlobRangeStreamMD5', [], suite.isMocked) + '.test';
            var stream = fs.createWriteStream(downloadedFileName);
            blobService.getBlobToStream(containerName, blobName, stream, downloadOptions, function (error) {
              assert.equal(error, null);
              
              getFileMD5(downloadedFileName, function (fileInfo) {
                assert.strictEqual(fileInfo.contentMD5, baseMD5);
                try {
                  fs.unlinkSync(downloadedFileName);
                  fs.unlinkSync(fileNameSource);
                } catch (e) { }
                done();
              });
            });
          });
        });
      });
    });

    runOrSkip('should download a range of block blob to local file', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlockBlobRangeLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, false, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }

          var downloadOptions = { parallelOperationThreadCount : 5, rangeStart: 100, rangeEnd: 70000000 };
          var downloadedFileName = testutil.generateId('getBlockBlobRange', [], suite.isMocked) + '.test';
          blobService.getBlobToLocalFile(containerName, blobName, downloadedFileName, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });
  });

  describe('createBlockBlobFromStream', function() {
    var len;
    var stream;

    before(function (done) {
      blockBlobContentMD5 = writeFile(blockFileName, fileText);
      var zeroBuffer = new Buffer(0);
      zeroFileContentMD5 = writeFile(zeroSizeFileName, zeroBuffer);
      done();
    });

    beforeEach(function (done) {
      len = Buffer.byteLength(fileText);
      stream = rfs.createReadStream(blockFileName);
      done();
    });

    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, blockBlobName, function(error) {
        done();
      });
    });

    it('should work with basic file stream', function(done) {
      blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, len, uploadOptions, function (err, blob) {
        assert.equal(err, null);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, blockBlobName, function(err, blob) {
          assert.equal(blob.contentMD5, blockBlobContentMD5);

          blobService.getBlobToText(containerName, blockBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, fileText);
            done();
          });
        });
      });
    });

    it('should work with contentMD5 in options', function(done) {
      var options = {
        blockIdPrefix: blockIdPrefix,
        contentMD5: blockBlobContentMD5
      };

      blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, len, options, function (err) {
        assert.equal(err, null);
        blobService.getBlobProperties(containerName, blockBlobName, function(err, blob) {
          assert.equal(blob.contentMD5, blockBlobContentMD5);
          done();
        });
      });
    });

    it('should work with the speed summary in options', function(done) {
      var speedSummary = new azure.BlobService.SpeedSummary();
      var options = {
        blockIdPrefix: blockIdPrefix,
        speedSummary: speedSummary
      };

      blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, len, options, function (err) {
        assert.equal(err, null);
        assert.equal(speedSummary.getTotalSize(false), Buffer.byteLength(fileText));
        assert.equal(speedSummary.getCompleteSize(false), Buffer.byteLength(fileText));
        assert.equal(speedSummary.getCompletePercent(), '100.0');
        done();
      });
    });

    it('should work with content type', function (done) {
      var blobOptions = { contentType: 'text' };

      blobService.createBlockBlobFromStream(containerName, blockBlobName, rfs.createReadStream(blockFileName), fileText.length, blobOptions, function (uploadError, blobResponse, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResponse, null);
        assert.ok(uploadResponse.isSuccessful);

        blobService.getBlobToText(containerName, blockBlobName, function (downloadErr, blobTextResponse) {
          assert.equal(downloadErr, null);
          assert.equal(blobTextResponse, fileText);

          blobService.getBlobProperties(containerName, blockBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobOptions.contentType, blobGetResponse.contentType);

            done();
          });
        });
      });
    });

    runOrSkip('should work with stream size (<= 32MB) which is a part of the read stream', function (done) {
      var options = {
        storeBlobContentMD5: false,
        useTransactionalMD5: false
      };
      var buffer = new Buffer(15 * 1024 * 1024);
      var tempBuffer = new Buffer(10);
      var uploadLength = buffer.length + tempBuffer.length;
      buffer.fill(1);
      tempBuffer.fill(2);
      var internalHash = crypto.createHash('md5');
      internalHash.update(buffer);
      internalHash.update(tempBuffer);
      var expectedMD5 = internalHash.digest('base64');
      var writeStream = fs.createWriteStream(blockFileName);
      writeStream.write(buffer, function () {
        buffer.fill(2);
        writeStream.write(buffer, function () {
          buffer.fill(3);
          writeStream.write(buffer, function () {
            writeStream.end();
          });
        });
      });
      
      writeStream.on('finish', function () {
        var stream = rfs.createReadStream(blockFileName);
        blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, uploadLength, options, function (err) {
          assert.equal(err, null);
          blobService.getBlobProperties(containerName, blockBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobGetResponse.contentLength, uploadLength);
            assert.equal(blobGetResponse.contentMD5, expectedMD5);
            
            // Calculate the MD5 by the library
            options.storeBlobContentMD5 = true;
            options.useTransactionalMD5 = true;
            stream = rfs.createReadStream(blockFileName);
            blobService.createBlockBlobFromStream(containerName, blockBlobName+1, stream, uploadLength, options, function (err) {
              assert.equal(err, null);
              blobService.getBlobProperties(containerName, blockBlobName+1, function (getBlobPropertiesErr, blobGetResponse) {
                assert.equal(getBlobPropertiesErr, null);
                assert.notEqual(blobGetResponse, null);
                assert.equal(blobGetResponse.contentLength, uploadLength);
                assert.equal(blobGetResponse.contentMD5, expectedMD5);
    
                done();
              });
            });
          });
        });
      });
    });
    
    runOrSkip('should work with stream size (> 32MB) which is a part of the read stream', function (done) {
      var options = {
        storeBlobContentMD5: true,
        useTransactionalMD5: true
      };
      var expectedMD5;
      var buffer = new Buffer(20 * 1024 * 1024);
      var tempBuffer = new Buffer(10);
      var uploadLength = buffer.length * 2 + tempBuffer.length;
      buffer.fill(1);
      var internalHash = crypto.createHash('md5');
      internalHash.update(buffer);
      var writeStream = fs.createWriteStream(blockFileName);
      writeStream.write(buffer, function () {
        buffer.fill(2);
        internalHash.update(buffer);
        writeStream.write(buffer, function () {
          buffer.fill(3);
          tempBuffer.fill(3);
          internalHash.update(tempBuffer);
          expectedMD5 = internalHash.digest('base64');
          writeStream.write(buffer, function () {
            writeStream.end();
          });
        });
      });
      
      writeStream.on('finish', function () {
        var stream = rfs.createReadStream(blockFileName);
        blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, uploadLength, options, function (err) {
          assert.equal(err, null);
          blobService.getBlobProperties(containerName, blockBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobGetResponse.contentLength, uploadLength);
            assert.equal(blobGetResponse.contentMD5, expectedMD5);

            done();
          });
        });
      });
    });

    runOrSkip('should work with parallelOperationsThreadCount in options', function (done) {
      var options = {
        blockIdPrefix: blockIdPrefix,
        parallelOperationThreadCount: 4
      };
      var expectedMD5;
      var buffer = new Buffer(25 * 1024 * 1024);
      var uploadLength = buffer.length * 3;
      buffer.fill(1);
      var internalHash = crypto.createHash('md5');
      internalHash.update(buffer);
      var writeStream = fs.createWriteStream(blockFileName);
      writeStream.write(buffer, function () {
        buffer.fill(2);
        internalHash.update(buffer);
        writeStream.write(buffer, function () {
          buffer.fill(3);
          internalHash.update(buffer);
          expectedMD5 = internalHash.digest('base64');
          writeStream.write(buffer, function () {
            writeStream.end();
          });
        });
      });

      writeStream.on('finish', function () {
        var stream = rfs.createReadStream(blockFileName);
        blobService.createBlockBlobFromStream(containerName, blockBlobName, stream, uploadLength, options, function (err) {
          assert.equal(err, null);
  
          blobService.getBlobProperties(containerName, blockBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobGetResponse.contentLength, uploadLength);
            assert.equal(blobGetResponse.contentMD5, expectedMD5);
  
            done();
          });
        });
      });
    });
  });

  describe('createPageBlobFromLocalFile', function() {
    var zeroFileContentMD5;
    before(function (done) {
      pageBlobBuffer.fill(1);
      pageBlobContentMD5 = writeFile(pageFileName, pageBlobBuffer);
      pageBlob2KBuffer.fill(1);
      pageBlob2KContentMD5 = writeFile(page2KFileName, pageBlob2KBuffer);
      var zeroBuffer = new Buffer(0);
      zeroFileContentMD5 = writeFile(zeroSizeFileName, zeroBuffer);
      done();
    });

    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, pageBlobName, function(error) {
        done();
      });
    });

    it('should work with basic file', function(done) {
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, function (err, blob) {
        assert.equal(err, null);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, pageBlobName, function (err1, blob) {
          assert.equal(err1, null);
          assert.equal(blob.contentMD5, undefined);

          blobService.getBlobToText(containerName, pageBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, pageBlobBuffer.toString());
            done();
          });
        });
      });
    });

    it('should work with speed summary', function(done) {
      var speedSummary = blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (err1, blob) {
          assert.equal(err1, null);
          assert.equal(blob.contentMD5, undefined);
          assert.equal(speedSummary.getTotalSize(false), 1024);
          assert.equal(speedSummary.getCompleteSize(false), 1024);
          assert.equal(speedSummary.getCompletePercent(), '100.0');
          done();
        });
      });
    });

    it('should set content md5', function(done) {
      var options = { storeBlobContentMD5 : true };

      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (getErr, blob) {
          assert.equal(getErr, null);
          assert.equal(blob.contentMD5, pageBlobContentMD5);
          done();
        });
      });
    });

    it('should overwrite the existing page blob', function(done) {
      var options = { storeBlobContentMD5 : true };

      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (err2, blob1) {
          assert.equal(err2, null);
          assert.notEqual(blob1, null);
          assert.equal(blob1.contentMD5, pageBlobContentMD5);
          assert.equal(blob1.contentLength, 1024);
          options.contentMD5Header = null;

          blobService.createPageBlobFromLocalFile(containerName, pageBlobName, page2KFileName, options, function (err3) {
            assert.equal(err3, null);

            blobService.getBlobProperties(containerName, pageBlobName, function (err4, blob2) {
              assert.equal(err4, null);
              assert.notEqual(blob2, null);
              assert.equal(blob2.contentLength, 2 * 1024);
              assert.equal(blob2.contentMD5, pageBlob2KContentMD5);
              done();
            });
          });
        });
      });
    });

    it('should work with zero size file', function(done) {
      uploadOptions.storeBlobContentMD5 = true;
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, zeroSizeFileName, uploadOptions, function (err1) {
        assert.equal(err1, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (err2, blob) {
          assert.equal(err2, null);
          assert.equal(blob.contentLength, 0);
          assert.equal(blob.contentMD5, zeroFileContentMD5);
          done();
        });
      });
    });

    it('should work with not existing file', function(done) {
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, notExistFileName, uploadOptions, function (err) {
        assert.notEqual(err, null);
        assert.equal(path.basename(err.path), notExistFileName);

        blobService.doesBlobExist(containerName, pageBlobName, function (existsErr, exists) {
          assert.equal(existsErr, null);
          assert.equal(exists, false);
          done();
        });
      });
    });

    it('should work with metadata', function(done) {
      var options = {
        storeBlobContentMD5 : true,
        useTransactionalMD5 : true,
        metadata: { color: 'blue' }
      };
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, zeroSizeFileName, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentLength, 0);
          assert.equal(blob.contentMD5, zeroFileContentMD5);
          assert.notEqual(blob.metadata, null);
          assert.equal(blob.metadata.color, options.metadata.color);
          done();
        });
      });
    });
    
    runOrSkip('should have same md5 with range-based downloading to local file', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlobRangeLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        var baseMD5 = fileInfo.contentMD5;
        uploadOptions.parallelOperationThreadCount = 5;
        uploadOptions.storeBlobContentMD5 = true;
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          
          blobService.getBlobProperties(containerName, blobName, function (error, blobProperties) {
            assert.equal(error, null);
            assert.notEqual(blobProperties, null);
            assert.strictEqual(blobProperties.contentMD5, baseMD5);
            
            var downloadOptions = { parallelOperationThreadCount : 5 };
            blobService.getBlobToLocalFile(containerName, blobName, fileNameSource, downloadOptions, function (error) {
              assert.equal(error, null);
              
              getFileMD5(fileNameSource, function (fileInfo) {
                assert.strictEqual(fileInfo.contentMD5, baseMD5);
                try { fs.unlinkSync(fileNameSource); } catch (e) { }
                done();
              });
            });
          });
        });
      });
    });

    runOrSkip('should have same md5 with range-based downloading to stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlobRangeStreamLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        var baseMD5 = fileInfo.contentMD5;
        uploadOptions.parallelOperationThreadCount = 5;
        uploadOptions.storeBlobContentMD5 = true;
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          
          blobService.getBlobProperties(containerName, blobName, function (error, blobProperties) {
            assert.equal(error, null);
            assert.notEqual(blobProperties, null);
            assert.strictEqual(blobProperties.contentMD5, baseMD5);

            var downloadOptions = { parallelOperationThreadCount : 5 };
            var downloadedFileName = testutil.generateId('getBlobRangeStream', [], suite.isMocked) + '.test';
            var stream = fs.createWriteStream(downloadedFileName);
            blobService.getBlobToStream(containerName, blobName, stream, downloadOptions, function (error) {
              assert.equal(error, null);
              
              getFileMD5(downloadedFileName, function (fileInfo) {
                assert.strictEqual(fileInfo.contentMD5, baseMD5);
                try {
                  fs.unlinkSync(fileNameSource);
                  fs.unlinkSync(downloadedFileName);
                } catch (e) { }
                done();
              });
            });
          });
        });
      });
    });
    
    runOrSkip('should download a range of page blob to local file', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getPageBlobRangeLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }
          
          var downloadOptions = { parallelOperationThreadCount : 5 , rangeStart: 512, rangeEnd: 51199999 };
          var downloadedFileName = testutil.generateId('getPageBlobRange', [], suite.isMocked) + '.test';
          blobService.getBlobToLocalFile(containerName, blobName, downloadedFileName, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });

    runOrSkip('should download a range of page blob to stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getPageBlobRangeStreamLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createPageBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }
          
          var downloadOptions = { parallelOperationThreadCount : 5 , rangeStart: 512, rangeEnd: 51199999 };
          var downloadedFileName = testutil.generateId('getPageBlobRangeStream', [], suite.isMocked) + '.test';
          var stream = fs.createWriteStream(downloadedFileName);
          blobService.getBlobToStream(containerName, blobName, stream, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });
  });

  describe('createPageBlobFromStream', function() {
    //Most cases are in CreatePageBlobFromFile
    it('should work with basic file', function(done) {
      var stream = rfs.createReadStream(pageFileName);
      blobService.createPageBlobFromStream(containerName, pageBlobName, stream, 1024, function (err, blob) {
        assert.equal(err, null);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, pageBlobName, function (err1, blob) {
          assert.equal(err1, null);
          assert.equal(blob.contentMD5, undefined);

          blobService.getBlobToText(containerName, pageBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, pageBlobBuffer.toString());
            done();
          });
        });
      });
    });
    
    runOrSkip('should work with stream size which is a part of the read stream', function (done) {
      var options = {
        storeBlobContentMD5: true,
        useTransactionalMD5: true
      };
      var buffer = new Buffer(15 * 1024 * 1024);
      var tempBuffer = new Buffer(512);
      var uploadLength = buffer.length + tempBuffer.length;
      buffer.fill(1);
      tempBuffer.fill(2);
      var internalHash = crypto.createHash('md5');
      internalHash.update(buffer);
      internalHash.update(tempBuffer);
      var expectedMD5 = internalHash.digest('base64');
      var writeStream = fs.createWriteStream(pageFileName);
      writeStream.write(buffer, function () {
        buffer.fill(2);
        writeStream.write(buffer, function () {
          buffer.fill(3);
          writeStream.write(buffer, function () {
            writeStream.end();
          });
        });
      });
      
      writeStream.on('finish', function () {
        var stream = rfs.createReadStream(pageFileName);
        blobService.createPageBlobFromStream(containerName, pageBlobName, stream, uploadLength, options, function (err) {
          assert.equal(err, null);
          blobService.getBlobProperties(containerName, pageBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobGetResponse.contentLength, uploadLength);
            assert.equal(blobGetResponse.contentMD5, expectedMD5);

            done();
          });
        });
      });
    });

    runOrSkip('should work with parallelOperationsThreadCount in options', function(done) {
      var options = {
        parallelOperationThreadCount : 4
      };

      var buffer = new Buffer(65 * 1024 * 1024);
      buffer.fill(1);
      var stream = rfs.createReadStream(pageFileName);
      
      blobService.createPageBlobFromStream(containerName, pageBlobName, stream, buffer.length, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, pageBlobName, function (getBlobPropertiesErr, blobGetResponse) {
          assert.equal(getBlobPropertiesErr, null);
          assert.notEqual(blobGetResponse, null);
          assert.equal(blobGetResponse.contentLength, buffer.length);

          done();
        });
      });
    });
  });

  describe('createAppendBlobFromLocalFile', function () {
    var zeroFileContentMD5;
    before(function (done) {
      appendBlobBuffer.fill(1);
      appendBlobContentMD5 = writeFile(appendFileName, appendBlobBuffer);
      appendBlob2KBuffer.fill(1);
      appendBlob2KContentMD5 = writeFile(append2KFileName, appendBlob2KBuffer);
      var zeroBuffer = new Buffer(0);
      zeroFileContentMD5 = writeFile(zeroSizeFileName, zeroBuffer);
      done();
    });
    
    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, appendBlobName, function (error) {
        done();
      });
    });
    
    it('should work with basic file', function (done) {
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, appendBlobName, function (err1, blob) {
          assert.equal(err1, null);
          assert.equal(blob.contentMD5, undefined);
          assert.equal(blob.committedBlockCount, 1);
          
          blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, appendBlobBuffer.toString());
            done();
          });
        });
      });
    });   

    it('should work with speed summary', function (done) {
      var speedSummary = blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err) {
        assert.equal(err, null);
        
        blobService.getBlobProperties(containerName, appendBlobName, function (err1, blob) {
          assert.equal(err1, null);
          assert.equal(blob.contentMD5, undefined);
          assert.equal(speedSummary.getTotalSize(false), 1024);
          assert.equal(speedSummary.getCompleteSize(false), 1024);
          assert.equal(speedSummary.getCompletePercent(), '100.0');
          done();
        });
      });
    });
    
    it('should set content md5', function (done) {
      var options = { storeBlobContentMD5 : true };
      
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, options, function (err) {
        assert.equal(err, null);
        
        blobService.getBlobProperties(containerName, appendBlobName, function (getErr, blob) {
          assert.equal(getErr, null);
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          done();
        });
      });
    });
    
    it('should overwrite the existing append blob', function (done) {
      var options = { storeBlobContentMD5 : true };
      
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, options, function (err) {
        assert.equal(err, null);
        
        blobService.getBlobProperties(containerName, appendBlobName, function (err2, blob1) {
          assert.equal(err2, null);
          assert.notEqual(blob1, null);
          assert.equal(blob1.contentMD5, appendBlobContentMD5);
          assert.equal(blob1.contentLength, 1024);
          options.contentMD5Header = null;
          
          blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, append2KFileName, options, function (err3, blob) {
            assert.equal(err3, null);
            assert.equal(blob.appendOffset, 0);
            assert.equal(blob.committedBlockCount, 1);
            
            blobService.getBlobProperties(containerName, appendBlobName, function (err4, blob2) {
              assert.equal(err4, null);
              assert.notEqual(blob2, null);
              assert.equal(blob2.committedBlockCount, 1);
              assert.equal(blob2.contentLength, 2 * 1024);
              assert.equal(blob2.contentMD5, appendBlob2KContentMD5);
              done();
            });
          });
        });
      });
    });
    
    it('should work with zero size file', function (done) {
      uploadOptions.storeBlobContentMD5 = true;
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, zeroSizeFileName, uploadOptions, function (err) {
        assert.equal(err, null);
        done();
      });
    });
    
    it('should work with not existing file', function (done) {
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, notExistFileName, uploadOptions, function (err) {
        assert.notEqual(err, null);
        assert.equal(path.basename(err.path), notExistFileName);
        
        blobService.doesBlobExist(containerName, appendBlobName, function (existsErr, exists) {
          assert.equal(existsErr, null);
          assert.equal(exists, false);
          done();
        });
      });
    });

    it('should work with metadata', function(done) {
      var options = {
        storeBlobContentMD5 : true,
        metadata: { color: 'blue' }
      };
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, options, function (err) {
        assert.equal(err, null);

        blobService.getBlobProperties(containerName, appendBlobName, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentLength, 1024);
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          assert.notEqual(blob.metadata, null);
          assert.equal(blob.metadata.color, options.metadata.color);
          done();
        });
      });
    });

    runOrSkip('should download a range of append blob to local file', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getAppendBlobRangeLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 1;
        blobService.createAppendBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }
          
          var downloadOptions = { parallelOperationThreadCount : 5 , rangeStart: 512, rangeEnd: 51199999 };
          var downloadedFileName = testutil.generateId('getAppendBlobRange', [], suite.isMocked) + '.test';
          blobService.getBlobToLocalFile(containerName, blobName, downloadedFileName, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });
  });

  describe('createAppendBlobFromStream', function () {
    var len;
    var stream;
    
    before(function (done) {
      appendBlobContentMD5 = writeFile(appendFileName, fileText);
      var zeroBuffer = new Buffer(0);
      zeroFileContentMD5 = writeFile(zeroSizeFileName, zeroBuffer);
      done();
    });
    
    beforeEach(function (done) {
      len = Buffer.byteLength(fileText);
      stream = rfs.createReadStream(appendFileName);
      done();
    });
    
    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, appendBlobName, function (error) {
        done();
      });
    });
    
    it('should work with basic file stream', function (done) {
      uploadOptions.storeBlobContentMD5 = true;
      uploadOptions.useTransactionalMD5 = true;
      blobService.createAppendBlobFromStream(containerName, appendBlobName, stream, len, uploadOptions, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        blobService.getBlobProperties(containerName, appendBlobName, function (err, blob) {
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          
          blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
            assert.equal(downloadErr, null);
            assert.equal(blobTextResponse, fileText);
            done();
          });
        });
      });
    });
    
    it('should work with contentMD5 in options', function (done) {
      var options = {
        contentMD5 : appendBlobContentMD5,
        useTransactionalMD5: true,
        storeBlobContentMD5: true
      };
      blobService.createAppendBlobFromStream(containerName, appendBlobName, stream, len, options, function (err) {
        assert.equal(err, null);
        blobService.getBlobProperties(containerName, appendBlobName, function (err, blob) {
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          done();
        });
      });
    });
    
    it('should work with the speed summary in options', function (done) {
      var speedSummary = new azure.BlobService.SpeedSummary();
      var options = {
        speedSummary : speedSummary
      };
      
      blobService.createAppendBlobFromStream(containerName, appendBlobName, stream, len, options, function (err) {
        assert.equal(err, null);
        assert.equal(speedSummary.getTotalSize(false), Buffer.byteLength(fileText));
        assert.equal(speedSummary.getCompleteSize(false), Buffer.byteLength(fileText));
        assert.equal(speedSummary.getCompletePercent(), '100.0');
        done();
      });
    });
    
    it('should work with content type', function (done) {
      var blobOptions = { contentType: 'text' };
      
      blobService.createAppendBlobFromStream(containerName, appendBlobName, rfs.createReadStream(appendFileName), fileText.length, blobOptions, function (uploadError, blobResponse, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResponse, null);
        assert.ok(uploadResponse.isSuccessful);
        
        blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
          assert.equal(downloadErr, null);
          assert.equal(blobTextResponse, fileText);
          
          blobService.getBlobProperties(containerName, appendBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobOptions.contentType, blobGetResponse.contentType);
            
            done();
          });
        });
      });
    });
    
    runOrSkip('should work with stream size which is a part of the read stream', function (done) {
      var options = {
        storeBlobContentMD5: true,
        useTransactionalMD5: true
      };
      var buffer = new Buffer(15 * 1024 * 1024);
      var tempBuffer = new Buffer(512);
      var uploadLength = buffer.length + tempBuffer.length;
      buffer.fill(1);
      tempBuffer.fill(2);
      var internalHash = crypto.createHash('md5');
      internalHash.update(buffer);
      internalHash.update(tempBuffer);
      var expectedMD5 = internalHash.digest('base64');
      var writeStream = fs.createWriteStream(appendFileName);
      writeStream.write(buffer, function () {
        buffer.fill(2);
        writeStream.write(buffer, function () {
          buffer.fill(3);
          writeStream.write(buffer, function () {
            writeStream.end();
          });
        });
      });
      
      writeStream.on('finish', function () {
        var stream = rfs.createReadStream(appendFileName);
        blobService.createAppendBlobFromStream(containerName, appendBlobName, stream, uploadLength, options, function (err) {
          assert.equal(err, null);
          blobService.getBlobProperties(containerName, appendBlobName, function (getBlobPropertiesErr, blobGetResponse) {
            assert.equal(getBlobPropertiesErr, null);
            assert.notEqual(blobGetResponse, null);
            assert.equal(blobGetResponse.contentLength, uploadLength);
            assert.equal(blobGetResponse.contentMD5, expectedMD5);

            done();
          });
        });
      });
    });

    runOrSkip('should download a range of append blob to stream', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getAppendBlobRangeStreamLocal', [], suite.isMocked) + '.test';
      var fileSize = 97 * 1024 * 1024;  // Don't be a multiple of 4MB to cover more scenarios
      generateTempFile(fileNameSource, fileSize, true, function (fileInfo) {
        uploadOptions.parallelOperationThreadCount = 5;
        blobService.createAppendBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
          assert.equal(error, null);
          try { fs.unlinkSync(fileNameSource); } catch (e) { }
          
          var downloadOptions = { parallelOperationThreadCount : 5 , rangeStart: 512, rangeEnd: 51199999 };
          var downloadedFileName = testutil.generateId('getAppendBlobRangeStream', [], suite.isMocked) + '.test';
          var stream = fs.createWriteStream(downloadedFileName);
          blobService.getBlobToStream(containerName, blobName, stream, downloadOptions, function (error) {
            assert.equal(error, null);
            
            var content = fs.readFileSync(downloadedFileName);
            assert.equal(content.length, downloadOptions.rangeEnd - downloadOptions.rangeStart + 1);
            delete content;
            try { fs.unlinkSync(downloadedFileName); } catch (e) { }
            done();
          });
        });
      });
    });
  });

  describe('createAppendBlobFromText', function () {
    it('should work for small size from text', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked) + ' a';
      var blobText = 'Hello World';
      
      blobService.createAppendBlobFromText(containerName, blobName, blobText, function (uploadError, blobResult, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResult, null);
        assert.equal(blobResult.appendOffset, 0);
        assert.equal(blobResult.committedBlockCount, 1);
        assert.ok(uploadResponse.isSuccessful);
        
        blobService.getBlobToText(containerName, blobName, function (downloadErr, blobTextResponse) {
          assert.equal(downloadErr, null);
          assert.equal(blobTextResponse, blobText);
          
          done();
        });
      });
    });
    
    it('should store md5', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked) + ' a';
      var blobText = 'Hello World';
      var blobMD5 = azureutil.getContentMd5(blobText);
      var options = {
        storeBlobContentMD5: true
      };
      
      blobService.createAppendBlobFromText(containerName, blobName, blobText, options, function (uploadError, blobResult, uploadResponse) {
        assert.equal(uploadError, null);
        assert.notEqual(blobResult, null);
        assert.equal(blobResult.appendOffset, 0);
        assert.equal(blobResult.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blobResult);
        assert.ok(uploadResponse.isSuccessful);
        
        blobService.getBlobProperties(containerName, blobName, function (error4, blobProperties) {
          assert.equal(error4, null);
          assert.notEqual(blobProperties, null);
          assert.equal(blobProperties.contentMD5, blobMD5);
          
          done();
        });
      });
    });
    
    it('should work with access condition', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var blobText = 'hello';
      
      blobService.createAppendBlobFromText(containerName, blobName, blobText, function (error2) {
        assert.equal(error2, null);
        
        blobService.getBlobProperties(containerName, blobName, function (error4, blobProperties) {
          assert.equal(error4, null);
          
          var options = { accessConditions: { 'if-none-match': blobProperties.etag } };
          blobService.createAppendBlobFromText(containerName, blobName, blobText, options, function (error3) {
            assert.notEqual(error3, null);
            assert.equal(error3.code, Constants.StorageErrorCodeStrings.CONDITION_NOT_MET);
            
            done();
          });
        });
      });
    });
  });

  describe('appendFromLocalFile', function () {  
    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, appendBlobName, function (error) {
        done();
      });
    });

    it('should work with basic file', function (done) {
      appendBlobBuffer.fill(1);
      writeFile(appendFileName, appendBlobBuffer);

      var text = appendBlobBuffer.toString();
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        blobService.appendFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.committedBlockCount, 2);

          blobService.appendFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
            assert.equal(err, null);
            assert.equal(blob.committedBlockCount, 3);

            blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
              assert.equal(downloadErr, null);
              assert.equal(blobTextResponse, text + text + text);
              done();
            });
          });
        });
      });
    });
  });

  describe('appendFromStream', function () { 
    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, appendBlobName, function (error) {
        done();
      });
    });

    it('should append from stream', function (done) {
      appendBlobBuffer.fill(1);
      writeFile(appendFileName, appendBlobBuffer);

      var text = appendBlobBuffer.toString();
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        appendBlobBuffer.fill(2);
        writeFile(appendFileName, appendBlobBuffer);
        var text1 = appendBlobBuffer.toString();
        var stream1 = rfs.createReadStream(appendFileName);

        blobService.appendFromStream(containerName, appendBlobName, stream1, text.length, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.committedBlockCount, 2);

          appendBlobBuffer.fill(3);
          writeFile(appendFileName, appendBlobBuffer);
          var text2 = appendBlobBuffer.toString();        
          var stream2 = rfs.createReadStream(appendFileName);

          blobService.appendFromStream(containerName, appendBlobName, stream2, text.length, function (err, blob) {
            assert.equal(err, null);
            assert.equal(blob.committedBlockCount, 3);

            blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
              assert.equal(downloadErr, null);
              assert.equal(blobTextResponse, text + text1 + text2);
              done();
            });
          });
        });
      });
    }); 
    
    it('should append from stream with stream length is part of the stream', function (done) {
      appendBlobBuffer.fill(1);
      writeFile(appendFileName, appendBlobBuffer);

      var text = appendBlobBuffer.toString();
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        appendBlobBuffer.fill(2);
        writeFile(appendFileName, appendBlobBuffer);
        var text1 = appendBlobBuffer.toString().substring(0, appendBlobBuffer.length - 10);
        var stream1 = rfs.createReadStream(appendFileName);
        var appendLength1 = text1.length;
        
        blobService.appendFromStream(containerName, appendBlobName, stream1, appendLength1, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.committedBlockCount, 2);

          appendBlobBuffer.fill(3);
          writeFile(appendFileName, appendBlobBuffer);
          var text2 = appendBlobBuffer.toString().substring(0, appendBlobBuffer.length - 20);
          var stream2 = rfs.createReadStream(appendFileName);
          var appendLength2 = text2.length;
          
          blobService.appendFromStream(containerName, appendBlobName, stream2, appendLength2, function (err, blob) {
            assert.equal(err, null);
            assert.equal(blob.committedBlockCount, 3);

            blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
              assert.equal(downloadErr, null);
              assert.equal(blobTextResponse, text + text1 + text2);
              done();
            });
          });
        });
      });
    });
  });

  describe('appendFromText', function () {  
    afterEach(function (done) {
      blobService.deleteBlobIfExists(containerName, appendBlobName, function (error) {
        done();
      });
    });

    it('should append plain text', function (done) {
      appendBlobBuffer.fill(1);
      writeFile(appendFileName, appendBlobBuffer);

    var text = appendBlobBuffer.toString();
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, function (err, blob) {
        assert.equal(err, null);
        assert.equal(blob.appendOffset, 0);
        assert.equal(blob.committedBlockCount, 1);
        assertNoStalePropertyOnBlob(blob);

        var text1 = 'redblack';
        var text2 = 'yellowblue';
        blobService.appendFromText(containerName, appendBlobName, text1, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.committedBlockCount, 2);

          blobService.appendFromText(containerName, appendBlobName, text2, function (err, blob) {
            assert.equal(err, null);
            assert.equal(blob.committedBlockCount, 3);

            blobService.getBlobToText(containerName, appendBlobName, function (downloadErr, blobTextResponse) {
              assert.equal(downloadErr, null);
              assert.equal(blobTextResponse, text + text1 + text2);
              done();
            });
          });
        });
      });
    });
  });

  describe('getAppendBlobToFile', function () {
    var appendBlobName = 'appendblob-test-getblob';
    
    it('should work with basic append blob', function (done) {
      appendBlobContentMD5 = writeFile(appendFileName, fileText);
      uploadOptions.storeBlobContentMD5 = true;
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, uploadOptions, function (err) {
        assert.equal(err, null);
        
        blobService.getBlobToLocalFile(containerName, appendBlobName, downloadName, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.blobType, 'AppendBlob');
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          
          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);
          
          fs.readFile(downloadName, function (err, text) {
            assert.equal(text, fileText);
            done();
          });
        });
      });
    });
    
    it('should calculate content md5', function (done) {
      appendBlobContentMD5 = writeFile(appendFileName, fileText);
      uploadOptions.storeBlobContentMD5 = true;
      blobService.createAppendBlobFromLocalFile(containerName, appendBlobName, appendFileName, uploadOptions, function (err) {
        assert.equal(err, null);
        
        var options = { disableContentMD5Validation : false };
        blobService.getBlobToLocalFile(containerName, appendBlobName, downloadName, options, function (err, blob) {
          assert.equal(err, null);
          assert.equal(blob.blobType, 'AppendBlob');
          assert.equal(blob.contentMD5, appendBlobContentMD5);
          
          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);
          
          fs.readFile(downloadName, function (err, text) {
            assert.equal(text, fileText);
            done();
          });
        });
      });
    }); 
  });

  describe('getBlockBlobToFile', function() {
    var blockBlobName = 'blockblob-test-getblob';

    it('should work with basic block blob', function(done) {
      blockBlobContentMD5 = writeFile(blockFileName, fileText);
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, uploadOptions, function (err) {
        assert.equal(err, null);

        blobService.getBlobToLocalFile(containerName, blockBlobName, downloadName, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, blockBlobContentMD5);

          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);

          fs.readFile(downloadName, function (err, text) {
            assert.equal(text, fileText);
            done();
          });
        });
      });
    });

    it('should calculate content md5', function(done) {
      blockBlobContentMD5 = writeFile(blockFileName, fileText);
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, uploadOptions, function (err) {
        assert.equal(err, null);

        var options = {disableContentMD5Validation : false};
        blobService.getBlobToLocalFile(containerName, blockBlobName, downloadName, options, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, blockBlobContentMD5);

          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);

          fs.readFile(downloadName, function (err, text) {
            assert.equal(text, fileText);
            done();
          });
        });
      });
    });
    
    it('should skip the size check', function(done) {
      blockBlobContentMD5 = writeFile(blockFileName, fileText);
      blobService.createBlockBlobFromLocalFile(containerName, blockBlobName, blockFileName, uploadOptions, function (err) {
        assert.equal(err, null);

        var options = {disableContentMD5Validation : false};
        var elapsed1 = new Date().valueOf();
        blobService.getBlobToLocalFile(containerName, blockBlobName, downloadName, options, function(err, blob) {
          elapsed1 = new Date().valueOf() - elapsed1;
          assert.equal(err, null);
          assert.equal(blob.contentMD5, blockBlobContentMD5);

          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);

          fs.readFile(downloadName, function (err, text) {
            assert.equal(text, fileText);
            
            var elapsed2 = new Date().valueOf();
            options.skipSizeCheck = true;
            blobService.getBlobToLocalFile(containerName, blockBlobName, downloadName, options, function(err, blob) {
              elapsed2 = new Date().valueOf() - elapsed2;
              assert.ok(suite.isMocked ? true : elapsed1 > elapsed2);
              
              assert.equal(err, null);
              assert.equal(blob.contentMD5, blockBlobContentMD5);
    
              var exists = azureutil.pathExistsSync(downloadName);
              assert.equal(exists, true);
    
              fs.readFile(downloadName, function (err, text) {
                assert.equal(text, fileText);
                done();
              });
            });
          });
        });
      });
    });
    
    it('should download a block blob to a local file in chunks', function (done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameSource = testutil.generateId('getBlockBlobRangeStreamLocal', [], suite.isMocked) + '.test';
      var buffer = new Buffer(4 * 1024 * 1024 + 512); // Don't be a multiple of 4MB to cover more scenarios
      var originLimit = blobService.singleBlobPutThresholdInBytes;
      buffer.fill(0);
      writeFile(fileNameSource, buffer);
      blobService.singleBlobPutThresholdInBytes = 1024 * 1024;
      blobService.createBlockBlobFromLocalFile(containerName, blobName, fileNameSource, uploadOptions, function (error) {
        try { fs.unlinkSync(fileNameSource); } catch (e) { }
        assert.equal(error, null);
        
        var downloadOptions = { parallelOperationThreadCount : 2 };
        var downloadedFileName = testutil.generateId('getBlockBlobRangeStream', [], suite.isMocked) + '.test';
        blobService.getBlobToLocalFile(containerName, blobName, downloadedFileName, downloadOptions, function (error) {
          try { fs.unlinkSync(downloadedFileName); } catch (e) { }
          assert.equal(error, null);
          blobService.singleBlobPutThresholdInBytes = originLimit;
          done();
        });
      });
    });
  });

  describe('getPageBlobToFile', function() {
    var pageBlobName = 'pageblob-test-getblob';

    it('should work with basic page blob', function(done) {
      pageBlobBuffer.fill(1);
      pageBlobContentMD5 = writeFile(pageFileName, pageBlobBuffer);
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, {storeBlobContentMD5: true}, function (err) {
        assert.equal(err, null);
        blobService.getBlobToLocalFile(containerName, pageBlobName, downloadName, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, pageBlobContentMD5);

          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);

          fs.readFile(downloadName, function (err, text) {
            assert.equal(text.toString(), pageBlobBuffer.toString());
            done();
          });
        });
      });
    });

    it('should calculate content md5', function(done) {
      pageBlobBuffer.fill(1);
      pageBlobContentMD5 = writeFile(pageFileName, pageBlobBuffer);
      blobService.createPageBlobFromLocalFile(containerName, pageBlobName, pageFileName, {storeBlobContentMD5: true}, function (err) {
        assert.equal(err, null);        
        var options = {disableContentMD5Validation : false};
        blobService.getBlobToLocalFile(containerName, pageBlobName, downloadName, options, function(err, blob) {
          assert.equal(err, null);
          assert.equal(blob.contentMD5, pageBlobContentMD5);

          var exists = azureutil.pathExistsSync(downloadName);
          assert.equal(exists, true);

          fs.readFile(downloadName, function (err, text) {
            assert.equal(text.toString(), pageBlobBuffer.toString());
            done();
          });
        });
      });
    });
  });
  
  describe('createWriteStreamToBlob', function() {
    it('should work with basic stream for block blob', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('createWriteStreamToBlockBlob', [], suite.isMocked) + '.blocktest';
      var blobBuffer = new Buffer(99);
      blobBuffer.fill(1);

      // Write file so that it can be piped
      fs.writeFileSync(fileNameTarget, blobBuffer);
      
      // Pipe file to a blob
      var stream = rfs.createReadStream(fileNameTarget).pipe(blobService.createWriteStreamToBlockBlob(containerName, blobName, { blockIdPrefix: 'block' }));
      stream.on('close', function () {
        blobService.getBlobToText(containerName, blobName, function (err, text) {
          assert.equal(err, null);
          assert.equal(text, blobBuffer);
          try { fs.unlinkSync(fileNameTarget); } catch (e) {}
          done();
        });
      });
    });
    
    it('should throw error with basic stream for page blob when the length is not a multiple of 512', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('createWriteStreamToPageBlob', [], suite.isMocked) + '.pagetest';
      var length = 99;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill('b');
      // Write file so that it can be piped
      fs.writeFileSync(fileNameTarget, blobBuffer);
      // Pipe file to a blob
      assert.throws(function() {
        rfs.createReadStream(fileNameTarget).pipe(blobService.createWriteStreamToNewPageBlob(containerName, blobName, length));
      }, 'Page blob length must be multiple of 512');
      
      done();
    });
    
    it('should work with basic stream for page blob', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('createWriteStreamToPageBlob', [], suite.isMocked) + '.pagetest';
      var length = 2 * 1024 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill('a');
      pageBlobContentMD5 = writeFile(fileNameTarget, blobBuffer);
      var readable = rfs.createReadStream(fileNameTarget);
      var stream = blobService.createWriteStreamToNewPageBlob(containerName, blobName, length, {storeBlobContentMD5: true});
      readable.pipe(stream);
      stream.on('close', function () {
        blobService.getBlobToText(containerName, blobName, function (err, text) {
          assert.equal(err, null);
          assert.equal(text, blobBuffer);
          try { fs.unlinkSync(fileNameTarget); } catch (e) {}
          done();
        });
      });
    });
    
    it('should work with basic stream for append blob', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var fileNameTarget = testutil.generateId('createWriteStreamToAppendBlob', [], suite.isMocked) + '.appendtest';
      var length = 2 * 1024 * 1024;
      var blobBuffer = new Buffer(length);
      blobBuffer.fill('a');
      pageBlobContentMD5 = writeFile(fileNameTarget, blobBuffer);
      var readable = rfs.createReadStream(fileNameTarget);
      var stream = blobService.createWriteStreamToNewAppendBlob(containerName, blobName, length);
      readable.pipe(stream);
      stream.on('close', function () {
        blobService.getBlobToText(containerName, blobName, function (err, text) {
          assert.equal(err, null);
          assert.equal(text, blobBuffer);
          try { fs.unlinkSync(fileNameTarget); } catch (e) {}
          done();
        });
      });
    });

    it('should work with string for block blob', function(done) {
      var blobName = testutil.generateId(blobNamesPrefix, blobNames, suite.isMocked);
      var content = 'plainstring';
      var stream = blobService.createWriteStreamToBlockBlob(containerName, blobName, { blockIdPrefix: 'block' });
      stream.write(content, 'utf-8');
      stream.end();
      stream.on('finish', function () {
        blobService.getBlobToText(containerName, blobName, function (err, text) {
          blobService.logger.level = azure.Logger.LogLevels.INFO;
          assert.equal(err, null);
          assert.equal(text, content);
          done();
        });
      });
    });
  });
});