private void stitchBlobAndVerify(byte[] requestBody, List<ChunkInfo> expectedStitchedChunks,
      ThrowingConsumer<ExecutionException> errorChecker) throws Exception {
    // unlike stitch for blob IDs, one step permanent stitch named blob can be supported through the built-in update TTL
    // call
    for (long ttl : new long[]{TestUtils.TTL_SECS, Utils.Infinite_Time}) {
      JSONObject headers = new JSONObject();
      FrontendRestRequestServiceTest.setAmbryHeadersForPut(headers, ttl, !REF_CONTAINER.isCacheable(), SERVICE_ID,
          CONTENT_TYPE, OWNER_ID, null, null, "STITCH");
      RestRequest request = getRestRequest(headers, request_path, requestBody);
      RestResponseChannel restResponseChannel = new MockRestResponseChannel();
      FutureResult<Void> future = new FutureResult<>();
      idConverterFactory.lastInput = null;
      idConverterFactory.lastBlobProperties = null;
      idConverterFactory.lastConvertedId = null;
      namedBlobPutHandler.handle(request, restResponseChannel, future::done);
      if (errorChecker == null) {
        future.get(TIMEOUT_SECS, TimeUnit.SECONDS);
        // in id converter, the callback is invoked before the lastConvertedId is set, we need to wait
        // for a while before we can check the lastConvertedId.
        Thread.sleep(100);
        assertEquals("Unexpected location header", idConverterFactory.lastConvertedId,
            restResponseChannel.getHeader(RestUtils.Headers.LOCATION));
        InMemoryRouter.InMemoryBlob blob = router.getActiveBlobs().get(idConverterFactory.lastInput);
        assertEquals("List of chunks stitched does not match expected", expectedStitchedChunks,
            blob.getStitchedChunks());
        ByteArrayOutputStream expectedContent = new ByteArrayOutputStream();
        expectedStitchedChunks.stream()
            .map(chunkInfo -> router.getActiveBlobs().get(chunkInfo.getBlobId()).getBlob().array())
            .forEach(buf -> expectedContent.write(buf, 0, buf.length));
        assertEquals("Unexpected blob content stored", ByteBuffer.wrap(expectedContent.toByteArray()), blob.getBlob());
        //check actual size of stitched blob
        assertEquals("Unexpected blob size", Long.toString(getStitchedBlobSize(expectedStitchedChunks)),
            restResponseChannel.getHeader(RestUtils.Headers.BLOB_SIZE));
        assertEquals("Unexpected TTL in named blob DB", ttl,
            idConverterFactory.lastBlobProperties.getTimeToLiveInSeconds());
        assertEquals("Unexpected TTL in blob", ttl, blob.getBlobProperties().getTimeToLiveInSeconds());
      } else {
        TestUtils.assertException(ExecutionException.class, () -> future.get(TIMEOUT_SECS, TimeUnit.SECONDS),
            errorChecker);
      }
    }
  }
Make a stitch blob call using {@link PostBlobHandler} and verify the result of the operation.
@param requestBody the body of the stitch request to supply.
@param expectedStitchedChunks the expected chunks stitched together.
@param errorChecker if non-null, expect an exception to be thrown by the post flow and verify it using this
                    {@link ThrowingConsumer}.
@throws Exception
ambry-frontend/src/test/java/com/github/ambry/frontend/NamedBlobPutHandlerTest.java