@VisibleForTesting
  void refresh(final Consumer<InputStream> changeListener) {
    try {
      final HeadObjectResponse objectMetadata = s3Client.headObject(HeadObjectRequest.builder()
          .bucket(s3Bucket)
          .key(objectKey)
          .build());

      final String initialETag = lastETag.get();
      final String refreshedETag = objectMetadata.eTag();

      if (!StringUtils.equals(initialETag, refreshedETag) && lastETag.compareAndSet(initialETag, refreshedETag)) {
        try (final ResponseInputStream<GetObjectResponse> response = getObject()) {
          log.info("Object at s3://{}/{} has changed; new eTag is {} and object size is {} bytes",
              s3Bucket, objectKey, response.response().eTag(), response.response().contentLength());
          changeListener.accept(response);
        }
      }
    } catch (final Exception e) {
      log.warn("Failed to refresh monitored object", e);
    }
  }
@Test
  void refresh() {
    final S3Client s3Client = mock(S3Client.class);

    final String bucket = "s3bucket";
    final String objectKey = "greatest-smooth-jazz-hits-of-all-time.zip";

    //noinspection unchecked
    final Consumer<InputStream> listener = mock(Consumer.class);

    final S3ObjectMonitor objectMonitor = new S3ObjectMonitor(
        s3Client,
        bucket,
        objectKey,
        16 * 1024 * 1024,
        mock(ScheduledExecutorService.class),
        Duration.ofMinutes(1));

    final String uuid = UUID.randomUUID().toString();
    when(s3Client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(
        HeadObjectResponse.builder().eTag(uuid).build());
    final ResponseInputStream<GetObjectResponse> ris = responseInputStreamFromString("abc", uuid);
    when(s3Client.getObject(GetObjectRequest.builder().bucket(bucket).key(objectKey).build())).thenReturn(ris);

    objectMonitor.refresh(listener);
    objectMonitor.refresh(listener);

    verify(listener).accept(ris);
  }