@Override
    public void onCompletion(DecryptJob.DecryptJobResult result, Exception exception) {
      if (countDownLatch != null) {
        countDownLatch.countDown();
      }
      if (!expectException) {
        assertEquals("BlobId mismatch ", blobId, result.getBlobId());
        assertNull("Exception shouldn't have been thrown to decrypt contents for " + blobId, exception);
        if (unencryptedContent != null) {
          ByteBuf decryptedBlobContent = result.getDecryptedBlobContent();
          assertNotNull("Decrypted contents should not be null", decryptedBlobContent);
          byte[] decryptedBlobContentArray = new byte[decryptedBlobContent.readableBytes()];
          decryptedBlobContent.readBytes(decryptedBlobContentArray);
          byte[] unencryptedContentArray = new byte[unencryptedContent.readableBytes()];
          unencryptedContent.readBytes(unencryptedContentArray);
          assertArrayEquals("Decrypted content and plain bytes should match", unencryptedContentArray,
              decryptedBlobContentArray);
          decryptedBlobContent.release();
          unencryptedContent.release();
        }
        if (unencryptedUserMetadata != null) {
          assertNotNull("Decrypted userMetadata should not be null", result.getDecryptedUserMetadata());
          assertArrayEquals("Decrypted userMetadata and plain bytes should match", unencryptedUserMetadata.array(),
              result.getDecryptedUserMetadata().array());
        }
      } else {
        assertNotNull("Exception should have been thrown to decrypt contents for " + blobId, exception);
        assertTrue("Exception cause should have been GeneralSecurityException",
            exception instanceof GeneralSecurityException);
        assertNull("Result should have been null", result);
      }
    }
Decrypt callback verifier. Verifies the decrypted content matches raw content on successful completion.
Else, verifies the exception is set correctly.
ambry-router/src/test/java/com/github/ambry/router/CryptoJobHandlerTest.java