void testRouterBasicForStitchedBlob() throws Exception {
    // More extensive test for puts present elsewhere - these statements are here just to exercise the flow within the
    // NonBlockingRouter class, and to ensure that operations submitted to a router eventually completes.
    LinkedList<String> blobIds = new LinkedList<>();
    for (int i = 0; i < 2; i++) {
      setOperationParams();
      String blobId = router.putBlob(putBlobProperties, putUserMetadata, putChannel, putOptionsForChunkedUpload).get();
      logger.debug("Put blob {}", blobId);
      blobIds.add(blobId);
    }
    setOperationParams();
    String stitchedBlobId = router.stitchBlob(putBlobProperties, putUserMetadata, blobIds.stream()
        .map(blobId -> new ChunkInfo(blobId, PUT_CONTENT_SIZE, Utils.Infinite_Time, null))
        .collect(Collectors.toList())).get();

    // Test router for individual blob operations on the stitched blob's chunks.
    router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().build()).get();
    router.updateBlobTtl(stitchedBlobId, null, Utils.Infinite_Time).get();
    router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().build()).get();
    router.getBlob(stitchedBlobId,
        new GetBlobOptionsBuilder().operationType(GetBlobOptions.OperationType.BlobInfo).build()).get();
    router.deleteBlob(stitchedBlobId, null).get();
    try {
      router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().build()).get();
      fail("Get blob should fail");
    } catch (ExecutionException e) {
      RouterException r = (RouterException) e.getCause();
      Assert.assertEquals("BlobDeleted error is expected", RouterErrorCode.BlobDeleted, r.getErrorCode());
    }
    router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().getOption(GetOption.Include_Deleted_Blobs).build())
        .get();
    router.getBlob(stitchedBlobId, new GetBlobOptionsBuilder().getOption(GetOption.Include_All).build()).get();

    Consumer<Callable<Future>> deletedBlobHandlerConsumer = (Callable<Future> c) -> {
      try {
        c.call().get();
      } catch (Exception ex) {
        assertTrue(ex.getCause() instanceof RouterException);
        assertEquals(RouterErrorCode.BlobDeleted, ((RouterException) ex.getCause()).getErrorCode());
      }
    };
    for (String blobId : blobIds) {
      deletedBlobHandlerConsumer.accept(() -> router.getBlob(blobId, new GetBlobOptionsBuilder().build()));
      deletedBlobHandlerConsumer.accept(() -> router.updateBlobTtl(blobId, null, Utils.Infinite_Time));
      router.deleteBlob(blobId, null).get();
      router.getBlob(blobId, new GetBlobOptionsBuilder().getOption(GetOption.Include_Deleted_Blobs).build()).get();
      router.getBlob(blobId, new GetBlobOptionsBuilder().getOption(GetOption.Include_All).build()).get();
    }
  }
Test Router with a single scaling unit for stitched blob operations.
ambry-router/src/test/java/com/github/ambry/router/NonBlockingRouterTest.java