private void testStorePuts(boolean requireEncryption) throws Exception {
    setupCloudStore(true, requireEncryption, defaultCacheLimit, true);
    // Put blobs with and without expiration and encryption
    MockMessageWriteSet messageWriteSet = new MockMessageWriteSet();
    int count = 1;
    int expectedUploads = 0;
    long expectedBytesUploaded = 0;
    int expectedEncryptions = 0;
    for (int j = 0; j < count; j++) {
      long size = Math.abs(random.nextLong()) % 10000;
      // Permanent and encrypted, should be uploaded and not reencrypted
      CloudTestUtil.addBlobToMessageSet(messageWriteSet, size, Utils.Infinite_Time, refAccountId, refContainerId, true,
          false, partitionId, operationTime, isVcr);
      expectedUploads++;
      expectedBytesUploaded += size;
      // Permanent and unencrypted
      CloudTestUtil.addBlobToMessageSet(messageWriteSet, size, Utils.Infinite_Time, refAccountId, refContainerId, false,
          false, partitionId, operationTime, isVcr);
      expectedUploads++;
      expectedBytesUploaded += size;
      if (requireEncryption) {
        expectedEncryptions++;
      }
    }

    /*
        Test 1: Put blob.
        V1: Success
        V2: Success
    */
    store.put(messageWriteSet);
    if (dest instanceof LatchBasedInMemoryCloudDestination) {
      LatchBasedInMemoryCloudDestination inMemoryDest = (LatchBasedInMemoryCloudDestination) dest;
      assertEquals("Unexpected blobs count", expectedUploads, inMemoryDest.getBlobsUploaded());
      assertEquals("Unexpected byte count", expectedBytesUploaded, inMemoryDest.getBytesUploaded());
    }
    verifyCacheHits(expectedUploads, 0);

    /*
      Test 2: Try to put the same blobs again (e.g. from another replica).
      V1: Ex thrown, which is probably wrong.
      V2: No ex thrown, Azure throws an err and V2 absorbs it to let repl advance. It would be wise to throw an error
      and let the repl layer handle it, but repl-layer doesn't.
     */
    messageWriteSet.resetBuffers();
    for (MessageInfo messageInfo : messageWriteSet.getMessageSetInfo()) {
      try {
        MockMessageWriteSet mockMessageWriteSet = new MockMessageWriteSet();
        CloudTestUtil.addBlobToMessageSet(mockMessageWriteSet, (BlobId) messageInfo.getStoreKey(),
            messageInfo.getSize(), messageInfo.getExpirationTimeInMs(), operationTime, isVcr);
        store.put(mockMessageWriteSet);
        if (ambryBackupVersion.equals(CloudConfig.AMBRY_BACKUP_VERSION_1)) {
          fail("Uploading already uploaded blob should throw error");
        }
      } catch (StoreException ex) {
        assertEquals(ex.getErrorCode(), StoreErrorCodes.AlreadyExist);
      }
    }
    int expectedSkips = isVcr ? expectedUploads : 0;
    if (dest instanceof LatchBasedInMemoryCloudDestination) {
      LatchBasedInMemoryCloudDestination inMemoryDest = (LatchBasedInMemoryCloudDestination) dest;
      assertEquals("Unexpected blobs count", expectedUploads, inMemoryDest.getBlobsUploaded());
      assertEquals("Unexpected byte count", expectedBytesUploaded, inMemoryDest.getBytesUploaded());
    }
    if (currentCacheLimit > 0) {
      assertEquals("Unexpected skipped count", expectedSkips, vcrMetrics.blobUploadSkippedCount.getCount());
    }
    verifyCacheHits(2 * expectedUploads, expectedUploads);

    /*
      Test 3: Try to upload a set of blobs containing duplicates
      V1: Failure expected
      V2: Failure expected
     */
    MessageInfo duplicateMessageInfo =
        messageWriteSet.getMessageSetInfo().get(messageWriteSet.getMessageSetInfo().size() - 1);
    CloudTestUtil.addBlobToMessageSet(messageWriteSet, (BlobId) duplicateMessageInfo.getStoreKey(),
        duplicateMessageInfo.getSize(), duplicateMessageInfo.getExpirationTimeInMs(), operationTime, isVcr);
    try {
      store.put(messageWriteSet);
    } catch (IllegalArgumentException iaex) {
      assertTrue(iaex.getMessage().startsWith("list contains duplicates"));
    }
    verifyCacheHits(2 * expectedUploads, expectedUploads);

    /*
      Test 4: Verify that a blob marked as deleted is not uploaded.
      This will never happen because repl logic never uploads a blob marked for deletion.
      snalli@: I am not fixing this test case or changing V2 to accommodate it.
     */
    MockMessageWriteSet deletedMessageWriteSet = new MockMessageWriteSet();
    CloudTestUtil.addBlobToMessageSet(deletedMessageWriteSet, 100, Utils.Infinite_Time, refAccountId, refContainerId,
        true, true, partitionId, operationTime, isVcr);
    store.put(deletedMessageWriteSet);
    expectedSkips++;
    verifyCacheHits(2 * expectedUploads, expectedUploads);
    if (currentCacheLimit > 0) {
      assertEquals(expectedSkips, vcrMetrics.blobUploadSkippedCount.getCount());
    }

    /*
        Test 5: Verify that a blob that is expiring soon is not uploaded for vcr but is uploaded for frontend.
        V1: Success
        V2: Success, V2 just uploads the blob. If it expires, then compaction will take care of it.
        These cloudConfigs were probably introduced to avoid throttling from CosmosDB and are not needed anymore.
        They just make deployment, testing and troubleshooting unnecessarily difficult, for eg. vcrMinTtlDays.
     */
    messageWriteSet = new MockMessageWriteSet();
    CloudTestUtil.addBlobToMessageSet(messageWriteSet, 100, operationTime + cloudConfig.vcrMinTtlDays - 1, refAccountId,
        refContainerId, true, false, partitionId, operationTime, isVcr);
    store.put(messageWriteSet);
    int expectedLookups = (2 * expectedUploads) + 1;
    if (isVcr) {
      expectedSkips++;
    } else {
      expectedUploads++;
    }
    verifyCacheHits(expectedLookups, expectedUploads);
    if (currentCacheLimit > 0) {
      assertEquals(expectedSkips, vcrMetrics.blobUploadSkippedCount.getCount());
    }
  }
Test the CloudBlobStore put method with specified encryption.
@param requireEncryption true for encrypted puts. false otherwise.
ambry-vcr/src/test/java/com/github/ambry/vcr/CloudBlobStoreTest.java