@Test
  public void testStitchGetUpdateDelete() throws Exception {
    try {
      AtomicReference<CountDownLatch> deletesDoneLatch = new AtomicReference<>();
      Set<String> deletedBlobs = ConcurrentHashMap.newKeySet();
      LoggingNotificationSystem deleteTrackingNotificationSystem = new LoggingNotificationSystem() {
        @Override
        public void onBlobDeleted(String blobId, String serviceId, Account account, Container container) {
          deletedBlobs.add(blobId);
          deletesDoneLatch.get().countDown();
        }
      };
      setRouter(getNonBlockingRouterProperties(localDcName), new MockServerLayout(mockClusterMap),
          deleteTrackingNotificationSystem);
      for (int intermediateChunkSize : new int[]{maxPutChunkSize, maxPutChunkSize / 2}) {
        for (LongStream chunkSizeStream : new LongStream[]{RouterTestHelpers.buildValidChunkSizeStream(
            3 * intermediateChunkSize, intermediateChunkSize), RouterTestHelpers.buildValidChunkSizeStream(
            3 * intermediateChunkSize + random.nextInt(intermediateChunkSize - 1) + 1, intermediateChunkSize)}) {
          // Upload data chunks
          ByteArrayOutputStream stitchedContentStream = new ByteArrayOutputStream();
          List<ChunkInfo> chunksToStitch = new ArrayList<>();
          PrimitiveIterator.OfLong chunkSizeIter = chunkSizeStream.iterator();
          while (chunkSizeIter.hasNext()) {
            long chunkSize = chunkSizeIter.nextLong();
            setOperationParams((int) chunkSize, TTL_SECS);
            String blobId = router.putBlob(putBlobProperties, putUserMetadata, putChannel,
                    new PutBlobOptionsBuilder().chunkUpload(true).maxUploadSize(PUT_CONTENT_SIZE).build())
                .get(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            long expirationTime = Utils.addSecondsToEpochTime(putBlobProperties.getCreationTimeInMs(),
                putBlobProperties.getTimeToLiveInSeconds());
            chunksToStitch.add(new ChunkInfo(blobId, chunkSize, expirationTime, null));
            stitchedContentStream.write(putContent);
          }
          byte[] expectedContent = stitchedContentStream.toByteArray();

          // Stitch the chunks together
          setOperationParams(0, TTL_SECS / 2);
          String stitchedBlobId = router.stitchBlob(putBlobProperties, putUserMetadata, chunksToStitch)
              .get(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);

          // Fetch the stitched blob
          GetBlobResult getBlobResult = router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().build())
              .get(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
          assertTrue("Blob properties must be the same",
              RouterTestHelpers.arePersistedFieldsEquivalent(putBlobProperties,
                  getBlobResult.getBlobInfo().getBlobProperties()));
          assertEquals("Unexpected blob size", expectedContent.length,
              getBlobResult.getBlobInfo().getBlobProperties().getBlobSize());
          assertArrayEquals("User metadata must be the same", putUserMetadata,
              getBlobResult.getBlobInfo().getUserMetadata());
          RouterTestHelpers.compareContent(expectedContent, null, getBlobResult.getBlobDataChannel());

          // TtlUpdate the blob.
          router.updateBlobTtl(stitchedBlobId, "update-service", Utils.Infinite_Time)
              .get(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);

          // Ensure that TTL was updated on the metadata blob and all data chunks
          Set<String> allBlobIds = chunksToStitch.stream().map(ChunkInfo::getBlobId).collect(Collectors.toSet());
          allBlobIds.add(stitchedBlobId);
          assertTtl(router, allBlobIds, Utils.Infinite_Time);

          // Delete and ensure that all stitched chunks are deleted
          deletedBlobs.clear();
          deletesDoneLatch.set(new CountDownLatch(chunksToStitch.size() + 1));
          router.deleteBlob(stitchedBlobId, "delete-service").get(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
          TestUtils.awaitLatchOrTimeout(deletesDoneLatch.get(), AWAIT_TIMEOUT_MS);
          assertEquals("Metadata chunk and all data chunks should be deleted", allBlobIds, deletedBlobs);
        }
      }
    } finally {
      if (router != null) {
        router.close();
        assertExpectedThreadCounts(0, 0);
      }
    }
  }
Test that stitched blobs are usable by the other router methods.
@throws Exception
ambry-router/src/test/java/com/github/ambry/router/NonBlockingRouterTest.java