From 823c3a884b842ce3c0c57021be93d9374a9e8fa2 Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Tue, 9 Dec 2025 12:40:20 -0500 Subject: [PATCH 1/7] Implement the DoDoesBucketExist --- .../multicloudj/blob/ali/AliBlobStore.java | 18 +++++++ .../blob/ali/AliBlobStoreTest.java | 54 +++++++++++++++++++ .../multicloudj/blob/aws/AwsBlobStore.java | 15 ++++++ .../blob/aws/async/AwsAsyncBlobStore.java | 14 +++++ .../blob/aws/AwsBlobStoreTest.java | 21 ++++++++ .../blob/aws/async/AwsAsyncBlobStoreTest.java | 27 ++++++++++ .../blob/async/client/AsyncBucketClient.java | 11 ++++ .../async/driver/AbstractAsyncBlobStore.java | 10 ++++ .../blob/async/driver/AsyncBlobStore.java | 6 +++ .../async/driver/BlobStoreAsyncBridge.java | 5 ++ .../multicloudj/blob/client/BucketClient.java | 15 ++++++ .../blob/driver/AbstractBlobStore.java | 10 ++++ .../multicloudj/blob/driver/BlobStore.java | 6 +++ .../blob/async/driver/TestAsyncBlobStore.java | 5 ++ .../blob/driver/TestBlobStore.java | 5 ++ .../multicloudj/blob/gcp/GcpBlobStore.java | 17 ++++++ .../blob/gcp/GcpBlobStoreTest.java | 52 ++++++++++++++++++ .../blob/gcp/async/GcpAsyncBlobStoreTest.java | 28 ++++++++++ 18 files changed, 319 insertions(+) diff --git a/blob/blob-ali/src/main/java/com/salesforce/multicloudj/blob/ali/AliBlobStore.java b/blob/blob-ali/src/main/java/com/salesforce/multicloudj/blob/ali/AliBlobStore.java index 4d97e6f61..9cbc43c4b 100644 --- a/blob/blob-ali/src/main/java/com/salesforce/multicloudj/blob/ali/AliBlobStore.java +++ b/blob/blob-ali/src/main/java/com/salesforce/multicloudj/blob/ali/AliBlobStore.java @@ -475,6 +475,24 @@ protected boolean doDoesObjectExist(String key, String versionId) { return ossClient.doesObjectExist(transformer.toMetadataRequest(key, versionId)); } + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + */ + @Override + protected boolean doDoesBucketExist() { + try { + return ossClient.doesBucketExist(bucket); + } catch (ServiceException e) { + if ("NoSuchBucket".equals(e.getErrorCode())) { + return false; + } + throw new SubstrateSdkException("Failed to check bucket existence", e); + } catch (ClientException e) { + throw new SubstrateSdkException("Failed to check bucket existence", e); + } + } + /** * Closes the underlying OSS client and releases any resources. */ diff --git a/blob/blob-ali/src/test/java/com/salesforce/multicloudj/blob/ali/AliBlobStoreTest.java b/blob/blob-ali/src/test/java/com/salesforce/multicloudj/blob/ali/AliBlobStoreTest.java index 03cb7cb53..28dc6a507 100644 --- a/blob/blob-ali/src/test/java/com/salesforce/multicloudj/blob/ali/AliBlobStoreTest.java +++ b/blob/blob-ali/src/test/java/com/salesforce/multicloudj/blob/ali/AliBlobStoreTest.java @@ -5,6 +5,7 @@ import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; +import com.aliyun.oss.ServiceException; import com.aliyun.oss.internal.OSSHeaders; import com.aliyun.oss.model.AbortMultipartUploadRequest; import com.aliyun.oss.model.CompleteMultipartUploadRequest; @@ -84,12 +85,14 @@ import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; @@ -783,6 +786,57 @@ void testDoDoesObjectExist() { assertTrue(result); } + @Test + void testDoDoesBucketExist() { + doReturn(true).when(mockOssClient).doesBucketExist("bucket-1"); + + boolean result = ali.doDoesBucketExist(); + + verify(mockOssClient, times(1)).doesBucketExist("bucket-1"); + assertTrue(result); + } + + @Test + void testDoDoesBucketExist_BucketDoesNotExist() { + doReturn(false).when(mockOssClient).doesBucketExist("bucket-1"); + + boolean result = ali.doDoesBucketExist(); + + verify(mockOssClient, times(1)).doesBucketExist("bucket-1"); + assertFalse(result); + } + + @Test + void testDoDoesBucketExist_ThrowsNoSuchBucketException() { + com.aliyun.oss.ServiceException serviceException = mock(com.aliyun.oss.ServiceException.class); + doReturn("NoSuchBucket").when(serviceException).getErrorCode(); + doThrow(serviceException).when(mockOssClient).doesBucketExist("bucket-1"); + + boolean result = ali.doDoesBucketExist(); + + verify(mockOssClient, times(1)).doesBucketExist("bucket-1"); + assertFalse(result); + } + + @Test + void testDoDoesBucketExist_ThrowsOtherServiceException() { + com.aliyun.oss.ServiceException serviceException = mock(com.aliyun.oss.ServiceException.class); + doReturn("AccessDenied").when(serviceException).getErrorCode(); + doThrow(serviceException).when(mockOssClient).doesBucketExist("bucket-1"); + + assertThrows(com.salesforce.multicloudj.common.exceptions.SubstrateSdkException.class, () -> ali.doDoesBucketExist()); + verify(mockOssClient, times(1)).doesBucketExist("bucket-1"); + } + + @Test + void testDoDoesBucketExist_ThrowsClientException() { + ClientException clientException = mock(ClientException.class); + doThrow(clientException).when(mockOssClient).doesBucketExist("bucket-1"); + + assertThrows(com.salesforce.multicloudj.common.exceptions.SubstrateSdkException.class, () -> ali.doDoesBucketExist()); + verify(mockOssClient, times(1)).doesBucketExist("bucket-1"); + } + private UploadRequest getTestUploadRequest() { Map metadata = Map.of("key-1", "value-1"); Map tags = Map.of("tag-1", "tag-value-1"); diff --git a/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsBlobStore.java b/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsBlobStore.java index e9319f404..32a0f4dcf 100644 --- a/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsBlobStore.java +++ b/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsBlobStore.java @@ -51,6 +51,7 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; import software.amazon.awssdk.services.s3.model.HeadObjectRequest; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; @@ -488,6 +489,20 @@ protected boolean doDoesObjectExist(String key, String versionId) { } } + @Override + protected boolean doDoesBucketExist() { + try { + s3Client.headBucket(builder -> builder.bucket(bucket)); + return true; + } + catch(S3Exception e) { + if (e.statusCode() == 404) { + return false; + } + throw e; + } + } + /** * Closes the underlying S3 client and releases any resources. */ diff --git a/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStore.java b/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStore.java index b76943454..a0fffc9c8 100644 --- a/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStore.java +++ b/blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStore.java @@ -403,6 +403,20 @@ protected CompletableFuture doDoesObjectExist(String key, String versio }); } + @Override + protected CompletableFuture doDoesBucketExist() { + return client + .headBucket(builder -> builder.bucket(bucket)) + .thenApply(response -> true) + .exceptionally(e -> { + if (e.getCause() instanceof S3Exception && ((S3Exception) e.getCause()).statusCode() == 404) { + return false; + } else { + throw new SubstrateSdkException("Request failed. Reason=" + e.getMessage(), e); + } + }); + } + /** * Closes the underlying S3 async client and transfer manager, releasing any resources. */ diff --git a/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsBlobStoreTest.java b/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsBlobStoreTest.java index 7e4f55070..1f4852fc6 100644 --- a/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsBlobStoreTest.java +++ b/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsBlobStoreTest.java @@ -59,6 +59,8 @@ import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest; import software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketResponse; import software.amazon.awssdk.services.s3.model.HeadObjectRequest; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; @@ -1033,6 +1035,25 @@ void testDoDoesObjectExist() { assertFalse(result); } + @Test + void testDoDoesBucketExist() { + HeadBucketResponse mockResponse = mock(HeadBucketResponse.class); + when(mockS3Client.headBucket(ArgumentMatchers.>any())).thenReturn(mockResponse); + + boolean result = aws.doDoesBucketExist(); + + verify(mockS3Client, times(1)).headBucket(ArgumentMatchers.>any()); + assertTrue(result); + + // Verify the error state - bucket doesn't exist (404) + S3Exception mockException = mock(S3Exception.class); + doReturn(404).when(mockException).statusCode(); + doThrow(mockException).when(mockS3Client).headBucket(ArgumentMatchers.>any()); + + result = aws.doDoesBucketExist(); + assertFalse(result); + } + private S3Object mockObject(int index) { S3Object mockS3 = mock(S3Object.class); when(mockS3.key()).thenReturn("key-" + index); diff --git a/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStoreTest.java b/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStoreTest.java index 307ae8f53..a6fb64633 100644 --- a/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStoreTest.java +++ b/blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/async/AwsAsyncBlobStoreTest.java @@ -70,6 +70,8 @@ import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectTaggingRequest; import software.amazon.awssdk.services.s3.model.GetObjectTaggingResponse; +import software.amazon.awssdk.services.s3.model.HeadBucketRequest; +import software.amazon.awssdk.services.s3.model.HeadBucketResponse; import software.amazon.awssdk.services.s3.model.HeadObjectRequest; import software.amazon.awssdk.services.s3.model.HeadObjectResponse; import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; @@ -136,6 +138,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import org.mockito.ArgumentMatchers; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -1133,6 +1136,30 @@ void testDoDoesObjectExist() throws ExecutionException, InterruptedException { assertInstanceOf(SubstrateSdkException.class, assertThrows(ExecutionException.class, exceptionalResult::get).getCause()); } + @Test + void testDoDoesBucketExist() throws ExecutionException, InterruptedException { + HeadBucketResponse mockResponse = mock(HeadBucketResponse.class); + doReturn(future(mockResponse)).when(mockS3Client).headBucket(ArgumentMatchers.>any()); + + boolean result = aws.doDoesBucketExist().get(); + + verify(mockS3Client, times(1)).headBucket(ArgumentMatchers.>any()); + assertTrue(result); + + // Verify the error state - bucket doesn't exist (404) + S3Exception mockException = mock(S3Exception.class); + doReturn(404).when(mockException).statusCode(); + doReturn(CompletableFuture.failedFuture(mockException)).when(mockS3Client).headBucket(ArgumentMatchers.>any()); + result = aws.doDoesBucketExist().get(); + assertFalse(result); + + // Verify the unexpected error state + doReturn(CompletableFuture.failedFuture(mock(RuntimeException.class))).when(mockS3Client).headBucket(ArgumentMatchers.>any()); + var exceptionalResult = aws.doDoesBucketExist(); + assertTrue(exceptionalResult.isCompletedExceptionally()); + assertInstanceOf(SubstrateSdkException.class, assertThrows(ExecutionException.class, exceptionalResult::get).getCause()); + } + @Test void doDownloadDirectory() throws ExecutionException, InterruptedException { diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClient.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClient.java index 6d6e6a5b5..f8c07b6b0 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClient.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClient.java @@ -387,6 +387,17 @@ public CompletableFuture doesObjectExist(String key, String versionId) .exceptionally(this::handleException); } + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + * @throws SubstrateSdkException Thrown if the operation fails + */ + public CompletableFuture doesBucketExist() { + return blobStore + .doesBucketExist() + .exceptionally(this::handleException); + } + /** * Uploads the directory content to substrate-specific Blob storage * Note: Specifying the contentLength in the UploadRequest can dramatically improve upload efficiency diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStore.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStore.java index 04124bbf2..0e7a5ea5e 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStore.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStore.java @@ -280,6 +280,14 @@ public CompletableFuture doesObjectExist(String key, String versionId) return doDoesObjectExist(key, versionId); } + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture doesBucketExist() { + return doDoesBucketExist(); + } + /** * {@inheritDoc} */ @@ -352,6 +360,8 @@ public CompletableFuture deleteDirectory(String prefix) { protected abstract CompletableFuture doDoesObjectExist(String key, String versionId); + protected abstract CompletableFuture doDoesBucketExist(); + protected abstract CompletableFuture doDownloadDirectory(DirectoryDownloadRequest directoryDownloadRequest); protected abstract CompletableFuture doUploadDirectory(DirectoryUploadRequest directoryUploadRequest); diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AsyncBlobStore.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AsyncBlobStore.java index b441731bb..c5870f485 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AsyncBlobStore.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/AsyncBlobStore.java @@ -247,6 +247,12 @@ public interface AsyncBlobStore extends SdkService, AutoCloseable { */ CompletableFuture doesObjectExist(String key, String versionId); + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + */ + CompletableFuture doesBucketExist(); + /** * Downloads the directory content from substrate-specific Blob storage. * diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/BlobStoreAsyncBridge.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/BlobStoreAsyncBridge.java index 7bfa60e28..03823d0f0 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/BlobStoreAsyncBridge.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/async/driver/BlobStoreAsyncBridge.java @@ -220,6 +220,11 @@ public CompletableFuture doesObjectExist(String key, String versionId) return CompletableFuture.supplyAsync(() -> blobStore.doesObjectExist(key, versionId), executorService); } + @Override + public CompletableFuture doesBucketExist() { + return CompletableFuture.supplyAsync(() -> blobStore.doesBucketExist(), executorService); + } + @Override public Class getException(Throwable t) { return blobStore.getException(t); diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/client/BucketClient.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/client/BucketClient.java index 3593fc1d2..400bfb12c 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/client/BucketClient.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/client/BucketClient.java @@ -484,6 +484,21 @@ public boolean doesObjectExist(String key, String versionId) { } } + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + * @throws SubstrateSdkException Thrown if the operation fails + */ + public boolean doesBucketExist() { + try { + return blobStore.doesBucketExist(); + } catch (Throwable t) { + Class exception = blobStore.getException(t); + ExceptionHandler.handleAndPropagate(exception, t); + return false; + } + } + /** * Closes the underlying blob store and releases any resources. */ diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/AbstractBlobStore.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/AbstractBlobStore.java index fbe68e8ff..6af16a963 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/AbstractBlobStore.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/AbstractBlobStore.java @@ -276,6 +276,14 @@ public boolean doesObjectExist(String key, String versionId) { return doDoesObjectExist(key, versionId); } + /** + * {@inheritDoc} + */ + @Override + public boolean doesBucketExist() { + return doDoesBucketExist(); + } + /** * {@inheritDoc} */ @@ -353,6 +361,8 @@ public void deleteDirectory(String prefix) { protected abstract boolean doDoesObjectExist(String key, String versionId); + protected abstract boolean doDoesBucketExist(); + protected DirectoryDownloadResponse doDownloadDirectory(DirectoryDownloadRequest directoryDownloadRequest) { throw new UnsupportedOperationException("Directory download is not supported by this substrate implementation"); } diff --git a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/BlobStore.java b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/BlobStore.java index fab41ccd9..e244b96d9 100644 --- a/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/BlobStore.java +++ b/blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/BlobStore.java @@ -242,6 +242,12 @@ public interface BlobStore extends SdkService, Provider { */ boolean doesObjectExist(String key, String versionId); + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + */ + boolean doesBucketExist(); + /** * Downloads a directory from the blob store * diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/TestAsyncBlobStore.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/TestAsyncBlobStore.java index 706987082..57d0e2f18 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/TestAsyncBlobStore.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/TestAsyncBlobStore.java @@ -179,6 +179,11 @@ protected CompletableFuture doDoesObjectExist(String key, String versio return null; } + @Override + protected CompletableFuture doDoesBucketExist() { + return CompletableFuture.completedFuture(false); + } + @Override public Class getException(Throwable t) { return SubstrateSdkException.class; diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/driver/TestBlobStore.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/driver/TestBlobStore.java index a2a4d211b..88f770f77 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/driver/TestBlobStore.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/driver/TestBlobStore.java @@ -143,6 +143,11 @@ protected boolean doDoesObjectExist(String key, String versionId) { return false; } + @Override + protected boolean doDoesBucketExist() { + return false; + } + @Override public Provider.Builder builder() { return new Builder(); diff --git a/blob/blob-gcp/src/main/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStore.java b/blob/blob-gcp/src/main/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStore.java index 9ca167a9b..23329312e 100644 --- a/blob/blob-gcp/src/main/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStore.java +++ b/blob/blob-gcp/src/main/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStore.java @@ -468,6 +468,23 @@ protected boolean doDoesObjectExist(String key, String versionId) { return storage.get(transformer.toBlobId(key, versionId)) != null; } + /** + * Determines if the bucket exists + * @return Returns true if the bucket exists. Returns false if it doesn't exist. + */ + @Override + protected boolean doDoesBucketExist() { + try { + Bucket bucketObj = storage.get(bucket); + return bucketObj != null; + } catch (StorageException e) { + if (e.getCode() == 404) { + return false; + } + throw new SubstrateSdkException("Failed to check bucket existence", e); + } + } + /** * Maximum number of objects that can be deleted in a single batch operation. * GCP supports up to 1000 objects per batch delete. diff --git a/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStoreTest.java b/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStoreTest.java index 5405b0620..93e839c0b 100644 --- a/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStoreTest.java +++ b/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStoreTest.java @@ -979,6 +979,58 @@ void testDoDoesObjectExist_WithNullVersionId() { verify(mockStorage).get(mockBlobId); } + @Test + void testDoDoesBucketExist_BucketExists() { + // Given + Bucket mockBucket = mock(Bucket.class); + when(mockStorage.get(TEST_BUCKET)).thenReturn(mockBucket); + + // When + boolean exists = gcpBlobStore.doDoesBucketExist(); + + // Then + assertTrue(exists); + verify(mockStorage).get(TEST_BUCKET); + } + + @Test + void testDoDoesBucketExist_BucketDoesNotExist_ReturnsNull() { + // Given + when(mockStorage.get(TEST_BUCKET)).thenReturn(null); + + // When + boolean exists = gcpBlobStore.doDoesBucketExist(); + + // Then + assertFalse(exists); + verify(mockStorage).get(TEST_BUCKET); + } + + @Test + void testDoDoesBucketExist_BucketDoesNotExist_Throws404() { + // Given + StorageException storageException = new StorageException(404, "Not Found"); + when(mockStorage.get(TEST_BUCKET)).thenThrow(storageException); + + // When + boolean exists = gcpBlobStore.doDoesBucketExist(); + + // Then + assertFalse(exists); + verify(mockStorage).get(TEST_BUCKET); + } + + @Test + void testDoDoesBucketExist_ThrowsOtherException() { + // Given + StorageException storageException = new StorageException(500, "Internal Server Error"); + when(mockStorage.get(TEST_BUCKET)).thenThrow(storageException); + + // When/Then + assertThrows(SubstrateSdkException.class, () -> gcpBlobStore.doDoesBucketExist()); + verify(mockStorage).get(TEST_BUCKET); + } + @Test void testGetException_WithSubstrateSdkException() { // Given diff --git a/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/async/GcpAsyncBlobStoreTest.java b/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/async/GcpAsyncBlobStoreTest.java index 6037d80da..0f0a90f5b 100644 --- a/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/async/GcpAsyncBlobStoreTest.java +++ b/blob/blob-gcp/src/test/java/com/salesforce/multicloudj/blob/gcp/async/GcpAsyncBlobStoreTest.java @@ -400,6 +400,34 @@ void testDoesObjectExist() throws Exception { verify(mockBlobStore).doesObjectExist(TEST_KEY, TEST_VERSION_ID); } + @Test + void testDoesBucketExist() throws Exception { + // Given + when(mockBlobStore.doesBucketExist()).thenReturn(true); + + // When + CompletableFuture result = gcpAsyncBlobStore.doesBucketExist(); + + // Then + Boolean exists = result.get(5, TimeUnit.SECONDS); + assertTrue(exists); + verify(mockBlobStore).doesBucketExist(); + } + + @Test + void testDoesBucketExist_BucketDoesNotExist() throws Exception { + // Given + when(mockBlobStore.doesBucketExist()).thenReturn(false); + + // When + CompletableFuture result = gcpAsyncBlobStore.doesBucketExist(); + + // Then + Boolean exists = result.get(5, TimeUnit.SECONDS); + assertFalse(exists); + verify(mockBlobStore).doesBucketExist(); + } + @Test void testGetTags() throws Exception { // Given From 570a6a76bfc38358ea3d81a8b04913949bda5dac Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 10:28:19 -0500 Subject: [PATCH 2/7] Update the conformance test --- .../blob/client/AbstractBlobStoreIT.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/AbstractBlobStoreIT.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/AbstractBlobStoreIT.java index d67cb845b..6f79eedae 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/AbstractBlobStoreIT.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/AbstractBlobStoreIT.java @@ -2590,6 +2590,22 @@ private void runDoesObjectExistTest(String key, boolean useVersionedBucket) thro } } + @Test + void testDoesBucketExist() { + // Test with a valid bucket - should return true + AbstractBlobStore blobStore = harness.createBlobStore(true, true, false); + BucketClient bucketClient = new BucketClient(blobStore); + Assertions.assertTrue(bucketClient.doesBucketExist()); + } + + @Test + void testDoesBucketExist_NonExistentBucket() { + // Test with a non-existent bucket - should return false + AbstractBlobStore blobStore = harness.createBlobStore(false, true, false); + BucketClient bucketClient = new BucketClient(blobStore); + Assertions.assertFalse(bucketClient.doesBucketExist()); + } + /** * Helper function for uploading to a presignedUrl */ From 4acd6cd197dab7a7514945430e61d922f3d39cec Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 10:28:48 -0500 Subject: [PATCH 3/7] Add record files for gcp --- .../resources/mappings/get-0gab94ptxl.json | 26 +++++++++++++++++++ .../resources/mappings/get-irwq8aw607.json | 26 +++++++++++++++++++ .../resources/mappings/get-ptttv5r3el.json | 26 +++++++++++++++++++ .../resources/mappings/get-rv3tln9sky.json | 26 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json diff --git a/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json b/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json new file mode 100644 index 000000000..abc840e2a --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json @@ -0,0 +1,26 @@ +{ + "id" : "53ad5ca5-5655-401b-a5e2-0dc2e553d25c", + "name" : "storage_v1_b_substrate-sdk-gcp-poc1-test-bucket", + "request" : { + "url" : "/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 200, + "body" : "{\n \"kind\": \"storage#bucket\",\n \"selfLink\": \"https://www.googleapis.com/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket\",\n \"id\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"name\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"projectNumber\": \"599653580068\",\n \"generation\": \"1758831398412595155\",\n \"metageneration\": \"1\",\n \"location\": \"US\",\n \"storageClass\": \"STANDARD\",\n \"etag\": \"CAE=\",\n \"timeCreated\": \"2025-09-25T20:16:38.678Z\",\n \"updated\": \"2025-09-25T20:16:38.678Z\",\n \"softDeletePolicy\": {\n \"retentionDurationSeconds\": \"604800\",\n \"effectiveTime\": \"2025-09-25T20:16:38.678Z\"\n },\n \"iamConfiguration\": {\n \"bucketPolicyOnly\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"uniformBucketLevelAccess\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"publicAccessPrevention\": \"inherited\"\n },\n \"locationType\": \"multi-region\",\n \"rpo\": \"DEFAULT\"\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "private, max-age=0, must-revalidate, no-transform", + "ETag" : "CAE=", + "X-GUploader-UploadID" : "AHVrFxMHplaBPndJPw-TtApYqxSpByJ0a3PABpjaVg3johVfm0glS8GpQJeoGwkBMYm9KSXz", + "Vary" : [ "Origin", "X-Origin" ], + "Expires" : "Wed, 10 Dec 2025 02:57:24 GMT", + "Date" : "Wed, 10 Dec 2025 02:57:24 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "53ad5ca5-5655-401b-a5e2-0dc2e553d25c", + "persistent" : true, + "insertionIndex" : 801 +} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json b/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json new file mode 100644 index 000000000..02e1c6980 --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json @@ -0,0 +1,26 @@ +{ + "id" : "5c23d31f-e743-42b2-b7e0-82cac5c17ba2", + "name" : "storage_v1_b_java-bucket-does-not-exist", + "request" : { + "url" : "/storage/v1/b/java-bucket-does-not-exist?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 404, + "body" : "{\n \"error\": {\n \"code\": 404,\n \"message\": \"The specified bucket does not exist.\",\n \"errors\": [\n {\n \"message\": \"The specified bucket does not exist.\",\n \"domain\": \"global\",\n \"reason\": \"notFound\"\n }\n ]\n }\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", + "X-GUploader-UploadID" : "AHVrFxO3EN7qAaiB29fsheXeqQpY9bw6xCAYkggdemfLMlsJqWapUBvnNqikNXu_9-_DBi8sAWDGOAE", + "Vary" : [ "Origin", "X-Origin" ], + "Pragma" : "no-cache", + "Expires" : "Mon, 01 Jan 1990 00:00:00 GMT", + "Date" : "Wed, 10 Dec 2025 02:57:25 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "5c23d31f-e743-42b2-b7e0-82cac5c17ba2", + "persistent" : true, + "insertionIndex" : 803 +} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json b/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json new file mode 100644 index 000000000..385b60f9a --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json @@ -0,0 +1,26 @@ +{ + "id" : "73855b1c-caaf-48cc-b237-c77507601603", + "name" : "storage_v1_b_substrate-sdk-gcp-poc1-test-bucket", + "request" : { + "url" : "/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 200, + "body" : "{\n \"kind\": \"storage#bucket\",\n \"selfLink\": \"https://www.googleapis.com/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket\",\n \"id\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"name\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"projectNumber\": \"599653580068\",\n \"generation\": \"1758831398412595155\",\n \"metageneration\": \"1\",\n \"location\": \"US\",\n \"storageClass\": \"STANDARD\",\n \"etag\": \"CAE=\",\n \"timeCreated\": \"2025-09-25T20:16:38.678Z\",\n \"updated\": \"2025-09-25T20:16:38.678Z\",\n \"softDeletePolicy\": {\n \"retentionDurationSeconds\": \"604800\",\n \"effectiveTime\": \"2025-09-25T20:16:38.678Z\"\n },\n \"iamConfiguration\": {\n \"bucketPolicyOnly\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"uniformBucketLevelAccess\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"publicAccessPrevention\": \"inherited\"\n },\n \"locationType\": \"multi-region\",\n \"rpo\": \"DEFAULT\"\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "private, max-age=0, must-revalidate, no-transform", + "ETag" : "CAE=", + "X-GUploader-UploadID" : "AHVrFxP-CUzgFckfjxSDb31DfIMX_egZZC-sHfCf0Gndf9SRiU03kMqzAXKkg1q8vP0VJVwK", + "Vary" : [ "Origin", "X-Origin" ], + "Expires" : "Wed, 10 Dec 2025 02:55:29 GMT", + "Date" : "Wed, 10 Dec 2025 02:55:29 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "73855b1c-caaf-48cc-b237-c77507601603", + "persistent" : true, + "insertionIndex" : 799 +} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json b/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json new file mode 100644 index 000000000..0b49ae035 --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json @@ -0,0 +1,26 @@ +{ + "id" : "79ee8843-0471-4fa7-941b-2a21cb60a989", + "name" : "storage_v1_b_java-bucket-does-not-exist", + "request" : { + "url" : "/storage/v1/b/java-bucket-does-not-exist?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 404, + "body" : "{\n \"error\": {\n \"code\": 404,\n \"message\": \"The specified bucket does not exist.\",\n \"errors\": [\n {\n \"message\": \"The specified bucket does not exist.\",\n \"domain\": \"global\",\n \"reason\": \"notFound\"\n }\n ]\n }\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", + "X-GUploader-UploadID" : "AHVrFxPC6Detgh2Prgq3lublnH95mquf6XyTJ36iYVjFuOsImmdD_23rZoK8wxFrMbjeH5jp6x96L3s", + "Vary" : [ "Origin", "X-Origin" ], + "Pragma" : "no-cache", + "Expires" : "Mon, 01 Jan 1990 00:00:00 GMT", + "Date" : "Wed, 10 Dec 2025 02:55:30 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "79ee8843-0471-4fa7-941b-2a21cb60a989", + "persistent" : true, + "insertionIndex" : 801 +} \ No newline at end of file From 1b270527b21db7275e3aceb8f5c7432dae9784e5 Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 12:46:53 -0500 Subject: [PATCH 4/7] Add record files for aws --- .../resources/mappings/head-pgwz6txhaw.json | 21 ++++++++++++++++ .../resources/mappings/head-ruowftbegm.json | 24 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 blob/blob-aws/src/test/resources/mappings/head-pgwz6txhaw.json create mode 100644 blob/blob-aws/src/test/resources/mappings/head-ruowftbegm.json diff --git a/blob/blob-aws/src/test/resources/mappings/head-pgwz6txhaw.json b/blob/blob-aws/src/test/resources/mappings/head-pgwz6txhaw.json new file mode 100644 index 000000000..d9b3ac98a --- /dev/null +++ b/blob/blob-aws/src/test/resources/mappings/head-pgwz6txhaw.json @@ -0,0 +1,21 @@ +{ + "id" : "6c44e456-44f3-477e-9866-b4713335e4cb", + "name" : "java-bucket-does-not-exist", + "request" : { + "url" : "/java-bucket-does-not-exist", + "method" : "HEAD" + }, + "response" : { + "status" : 404, + "headers" : { + "Server" : "AmazonS3", + "x-amz-request-id" : "S25MYAZ5WTF51AJ8", + "x-amz-id-2" : "4yw9URr1CZ7CQpeqEif9BaRFIRyp/WxIQiYWQ6eDdZRoT8CJXtnoUhRf3u6WRkF8OYZTtjXLm/g6owSCjFfRE7F/0Ny35DW1LumP474xCbk=", + "Date" : "Wed, 10 Dec 2025 17:44:21 GMT", + "Content-Type" : "application/xml" + } + }, + "uuid" : "6c44e456-44f3-477e-9866-b4713335e4cb", + "persistent" : true, + "insertionIndex" : 595 +} \ No newline at end of file diff --git a/blob/blob-aws/src/test/resources/mappings/head-ruowftbegm.json b/blob/blob-aws/src/test/resources/mappings/head-ruowftbegm.json new file mode 100644 index 000000000..7c20ed426 --- /dev/null +++ b/blob/blob-aws/src/test/resources/mappings/head-ruowftbegm.json @@ -0,0 +1,24 @@ +{ + "id" : "5abebd0a-9030-4e7b-91aa-1409334a4b6b", + "name" : "chameleon-jcloud", + "request" : { + "url" : "/chameleon-jcloud", + "method" : "HEAD" + }, + "response" : { + "status" : 200, + "headers" : { + "Server" : "AmazonS3", + "x-amz-access-point-alias" : "false", + "x-amz-bucket-arn" : "arn:aws:s3:::chameleon-jcloud", + "x-amz-request-id" : "JTCB60A5F2ERKBDM", + "x-amz-id-2" : "VBMYlZ3W9WACjGa0VL6cjK5tNsgDdM6at9KorprY5u9IPJg1A+z4PCWxSDjzdkWQME2f9Sh/NK4=", + "x-amz-bucket-region" : "us-west-2", + "Date" : "Wed, 10 Dec 2025 17:43:54 GMT", + "Content-Type" : "application/xml" + } + }, + "uuid" : "5abebd0a-9030-4e7b-91aa-1409334a4b6b", + "persistent" : true, + "insertionIndex" : 594 +} \ No newline at end of file From e6de2e864a8ddc95f47147e6effe7a468d47ec45 Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 13:18:04 -0500 Subject: [PATCH 5/7] Improve test coverage --- .../async/client/AsyncBucketClientTest.java | 25 ++++++++++++++ .../driver/AbstractAsyncBlobStoreTest.java | 34 +++++++++++++++++++ .../blob/client/BucketClientTest.java | 27 +++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClientTest.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClientTest.java index 3e454c339..0a9e2f1be 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClientTest.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/client/AsyncBucketClientTest.java @@ -122,6 +122,7 @@ import static com.salesforce.multicloudj.blob.async.driver.TestAsyncBlobStore.PROVIDER_ID; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -644,6 +645,30 @@ void testDoDoesObjectExist() throws ExecutionException, InterruptedException { assertFailed(client.doesObjectExist("object-1", "version-1"), UnAuthorizedException.class); } + @Test + void testDoesBucketExist_ReturnsTrue() throws ExecutionException, InterruptedException { + doReturn(CompletableFuture.completedFuture(true)).when(mockBlobStore).doesBucketExist(); + boolean result = client.doesBucketExist().get(); + verify(mockBlobStore, times(1)).doesBucketExist(); + assertTrue(result); + } + + @Test + void testDoesBucketExist_ReturnsFalse() throws ExecutionException, InterruptedException { + doReturn(CompletableFuture.completedFuture(false)).when(mockBlobStore).doesBucketExist(); + boolean result = client.doesBucketExist().get(); + verify(mockBlobStore, times(1)).doesBucketExist(); + assertFalse(result); + } + + @Test + void testDoesBucketExist_ThrowsException() { + CompletableFuture failure = CompletableFuture.failedFuture(new RuntimeException()); + doReturn(failure).when(mockBlobStore).doesBucketExist(); + assertFailed(client.doesBucketExist(), UnAuthorizedException.class); + verify(mockBlobStore, times(1)).doesBucketExist(); + } + @Test void testDownloadDirectory() throws ExecutionException, InterruptedException { DirectoryDownloadRequest request = DirectoryDownloadRequest.builder() diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStoreTest.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStoreTest.java index 15ba4c89e..ca7cefbc6 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStoreTest.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/async/driver/AbstractAsyncBlobStoreTest.java @@ -437,6 +437,40 @@ void testDoDoesObjectExist() { verify(mockBlobStore, times(1)).doDoesObjectExist("object-1", "version-1"); } + @Test + void testDoDoesBucketExist() { + mockBlobStore.doesBucketExist(); + verify(mockBlobStore, times(1)).doDoesBucketExist(); + } + + @Test + void testDoDoesBucketExist_ReturnsTrue() throws ExecutionException, InterruptedException { + when(mockBlobStore.doDoesBucketExist()).thenReturn(CompletableFuture.completedFuture(true)); + CompletableFuture result = mockBlobStore.doesBucketExist(); + assertTrue(result.get()); + verify(mockBlobStore, times(1)).doDoesBucketExist(); + } + + @Test + void testDoDoesBucketExist_ReturnsFalse() throws ExecutionException, InterruptedException { + when(mockBlobStore.doDoesBucketExist()).thenReturn(CompletableFuture.completedFuture(false)); + CompletableFuture result = mockBlobStore.doesBucketExist(); + assertFalse(result.get()); + verify(mockBlobStore, times(1)).doDoesBucketExist(); + } + + @Test + void testDoDoesBucketExist_WithException() { + RuntimeException expectedException = new RuntimeException("Bucket check failed"); + when(mockBlobStore.doDoesBucketExist()).thenReturn(CompletableFuture.failedFuture(expectedException)); + CompletableFuture result = mockBlobStore.doesBucketExist(); + ExecutionException exception = assertThrows(ExecutionException.class, () -> { + result.get(); + }); + assertEquals(expectedException, exception.getCause()); + verify(mockBlobStore, times(1)).doDoesBucketExist(); + } + @Test void testDoUploadDirectory() { DirectoryUploadRequest request = DirectoryUploadRequest.builder() diff --git a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/BucketClientTest.java b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/BucketClientTest.java index 465708921..6477b7d6c 100644 --- a/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/BucketClientTest.java +++ b/blob/blob-client/src/test/java/com/salesforce/multicloudj/blob/client/BucketClientTest.java @@ -40,8 +40,10 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -531,6 +533,31 @@ void testDoesObjectExist() { }); } + @Test + void testDoesBucketExist_ReturnsTrue() { + when(mockBlobStore.doesBucketExist()).thenReturn(true); + boolean result = client.doesBucketExist(); + verify(mockBlobStore, times(1)).doesBucketExist(); + assertTrue(result); + } + + @Test + void testDoesBucketExist_ReturnsFalse() { + when(mockBlobStore.doesBucketExist()).thenReturn(false); + boolean result = client.doesBucketExist(); + verify(mockBlobStore, times(1)).doesBucketExist(); + assertFalse(result); + } + + @Test + void testDoesBucketExist_ThrowsException() { + doThrow(RuntimeException.class).when(mockBlobStore).doesBucketExist(); + assertThrows(UnAuthorizedException.class, () -> { + client.doesBucketExist(); + }); + verify(mockBlobStore, times(1)).doesBucketExist(); + } + @Test void testBucketClientBuilderWithRetryConfig() { RetryConfig retryConfig = RetryConfig.builder() From c02559d7b98be03efd953ffd6844486a50a1dbfe Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 21:40:06 -0500 Subject: [PATCH 6/7] Revert "Add record files for gcp" This reverts commit 4acd6cd197dab7a7514945430e61d922f3d39cec. --- .../resources/mappings/get-0gab94ptxl.json | 26 ------------------- .../resources/mappings/get-irwq8aw607.json | 26 ------------------- .../resources/mappings/get-ptttv5r3el.json | 26 ------------------- .../resources/mappings/get-rv3tln9sky.json | 26 ------------------- 4 files changed, 104 deletions(-) delete mode 100644 blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json delete mode 100644 blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json delete mode 100644 blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json delete mode 100644 blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json diff --git a/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json b/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json deleted file mode 100644 index abc840e2a..000000000 --- a/blob/blob-gcp/src/test/resources/mappings/get-0gab94ptxl.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id" : "53ad5ca5-5655-401b-a5e2-0dc2e553d25c", - "name" : "storage_v1_b_substrate-sdk-gcp-poc1-test-bucket", - "request" : { - "url" : "/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket?projection=full", - "method" : "GET" - }, - "response" : { - "status" : 200, - "body" : "{\n \"kind\": \"storage#bucket\",\n \"selfLink\": \"https://www.googleapis.com/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket\",\n \"id\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"name\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"projectNumber\": \"599653580068\",\n \"generation\": \"1758831398412595155\",\n \"metageneration\": \"1\",\n \"location\": \"US\",\n \"storageClass\": \"STANDARD\",\n \"etag\": \"CAE=\",\n \"timeCreated\": \"2025-09-25T20:16:38.678Z\",\n \"updated\": \"2025-09-25T20:16:38.678Z\",\n \"softDeletePolicy\": {\n \"retentionDurationSeconds\": \"604800\",\n \"effectiveTime\": \"2025-09-25T20:16:38.678Z\"\n },\n \"iamConfiguration\": {\n \"bucketPolicyOnly\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"uniformBucketLevelAccess\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"publicAccessPrevention\": \"inherited\"\n },\n \"locationType\": \"multi-region\",\n \"rpo\": \"DEFAULT\"\n}\n", - "headers" : { - "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", - "Server" : "UploadServer", - "Cache-Control" : "private, max-age=0, must-revalidate, no-transform", - "ETag" : "CAE=", - "X-GUploader-UploadID" : "AHVrFxMHplaBPndJPw-TtApYqxSpByJ0a3PABpjaVg3johVfm0glS8GpQJeoGwkBMYm9KSXz", - "Vary" : [ "Origin", "X-Origin" ], - "Expires" : "Wed, 10 Dec 2025 02:57:24 GMT", - "Date" : "Wed, 10 Dec 2025 02:57:24 GMT", - "Content-Type" : "application/json; charset=UTF-8" - } - }, - "uuid" : "53ad5ca5-5655-401b-a5e2-0dc2e553d25c", - "persistent" : true, - "insertionIndex" : 801 -} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json b/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json deleted file mode 100644 index 02e1c6980..000000000 --- a/blob/blob-gcp/src/test/resources/mappings/get-irwq8aw607.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id" : "5c23d31f-e743-42b2-b7e0-82cac5c17ba2", - "name" : "storage_v1_b_java-bucket-does-not-exist", - "request" : { - "url" : "/storage/v1/b/java-bucket-does-not-exist?projection=full", - "method" : "GET" - }, - "response" : { - "status" : 404, - "body" : "{\n \"error\": {\n \"code\": 404,\n \"message\": \"The specified bucket does not exist.\",\n \"errors\": [\n {\n \"message\": \"The specified bucket does not exist.\",\n \"domain\": \"global\",\n \"reason\": \"notFound\"\n }\n ]\n }\n}\n", - "headers" : { - "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", - "Server" : "UploadServer", - "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", - "X-GUploader-UploadID" : "AHVrFxO3EN7qAaiB29fsheXeqQpY9bw6xCAYkggdemfLMlsJqWapUBvnNqikNXu_9-_DBi8sAWDGOAE", - "Vary" : [ "Origin", "X-Origin" ], - "Pragma" : "no-cache", - "Expires" : "Mon, 01 Jan 1990 00:00:00 GMT", - "Date" : "Wed, 10 Dec 2025 02:57:25 GMT", - "Content-Type" : "application/json; charset=UTF-8" - } - }, - "uuid" : "5c23d31f-e743-42b2-b7e0-82cac5c17ba2", - "persistent" : true, - "insertionIndex" : 803 -} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json b/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json deleted file mode 100644 index 385b60f9a..000000000 --- a/blob/blob-gcp/src/test/resources/mappings/get-ptttv5r3el.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id" : "73855b1c-caaf-48cc-b237-c77507601603", - "name" : "storage_v1_b_substrate-sdk-gcp-poc1-test-bucket", - "request" : { - "url" : "/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket?projection=full", - "method" : "GET" - }, - "response" : { - "status" : 200, - "body" : "{\n \"kind\": \"storage#bucket\",\n \"selfLink\": \"https://www.googleapis.com/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket\",\n \"id\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"name\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"projectNumber\": \"599653580068\",\n \"generation\": \"1758831398412595155\",\n \"metageneration\": \"1\",\n \"location\": \"US\",\n \"storageClass\": \"STANDARD\",\n \"etag\": \"CAE=\",\n \"timeCreated\": \"2025-09-25T20:16:38.678Z\",\n \"updated\": \"2025-09-25T20:16:38.678Z\",\n \"softDeletePolicy\": {\n \"retentionDurationSeconds\": \"604800\",\n \"effectiveTime\": \"2025-09-25T20:16:38.678Z\"\n },\n \"iamConfiguration\": {\n \"bucketPolicyOnly\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"uniformBucketLevelAccess\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"publicAccessPrevention\": \"inherited\"\n },\n \"locationType\": \"multi-region\",\n \"rpo\": \"DEFAULT\"\n}\n", - "headers" : { - "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", - "Server" : "UploadServer", - "Cache-Control" : "private, max-age=0, must-revalidate, no-transform", - "ETag" : "CAE=", - "X-GUploader-UploadID" : "AHVrFxP-CUzgFckfjxSDb31DfIMX_egZZC-sHfCf0Gndf9SRiU03kMqzAXKkg1q8vP0VJVwK", - "Vary" : [ "Origin", "X-Origin" ], - "Expires" : "Wed, 10 Dec 2025 02:55:29 GMT", - "Date" : "Wed, 10 Dec 2025 02:55:29 GMT", - "Content-Type" : "application/json; charset=UTF-8" - } - }, - "uuid" : "73855b1c-caaf-48cc-b237-c77507601603", - "persistent" : true, - "insertionIndex" : 799 -} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json b/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json deleted file mode 100644 index 0b49ae035..000000000 --- a/blob/blob-gcp/src/test/resources/mappings/get-rv3tln9sky.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id" : "79ee8843-0471-4fa7-941b-2a21cb60a989", - "name" : "storage_v1_b_java-bucket-does-not-exist", - "request" : { - "url" : "/storage/v1/b/java-bucket-does-not-exist?projection=full", - "method" : "GET" - }, - "response" : { - "status" : 404, - "body" : "{\n \"error\": {\n \"code\": 404,\n \"message\": \"The specified bucket does not exist.\",\n \"errors\": [\n {\n \"message\": \"The specified bucket does not exist.\",\n \"domain\": \"global\",\n \"reason\": \"notFound\"\n }\n ]\n }\n}\n", - "headers" : { - "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", - "Server" : "UploadServer", - "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", - "X-GUploader-UploadID" : "AHVrFxPC6Detgh2Prgq3lublnH95mquf6XyTJ36iYVjFuOsImmdD_23rZoK8wxFrMbjeH5jp6x96L3s", - "Vary" : [ "Origin", "X-Origin" ], - "Pragma" : "no-cache", - "Expires" : "Mon, 01 Jan 1990 00:00:00 GMT", - "Date" : "Wed, 10 Dec 2025 02:55:30 GMT", - "Content-Type" : "application/json; charset=UTF-8" - } - }, - "uuid" : "79ee8843-0471-4fa7-941b-2a21cb60a989", - "persistent" : true, - "insertionIndex" : 801 -} \ No newline at end of file From 393e6aa4dac93096230662cc542ee45ffe92a3b1 Mon Sep 17 00:00:00 2001 From: Barry Liu Date: Wed, 10 Dec 2025 21:42:28 -0500 Subject: [PATCH 7/7] Update the record files for gcp --- .../resources/mappings/get-eq9h2ytn0f.json | 26 +++++++++++++++++++ .../resources/mappings/get-pdnihrvmgo.json | 26 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-eq9h2ytn0f.json create mode 100644 blob/blob-gcp/src/test/resources/mappings/get-pdnihrvmgo.json diff --git a/blob/blob-gcp/src/test/resources/mappings/get-eq9h2ytn0f.json b/blob/blob-gcp/src/test/resources/mappings/get-eq9h2ytn0f.json new file mode 100644 index 000000000..5477cef71 --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-eq9h2ytn0f.json @@ -0,0 +1,26 @@ +{ + "id" : "ed5c97d1-8c90-4db2-ae7c-8fee37f52640", + "name" : "storage_v1_b_java-bucket-does-not-exist", + "request" : { + "url" : "/storage/v1/b/java-bucket-does-not-exist?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 404, + "body" : "{\n \"error\": {\n \"code\": 404,\n \"message\": \"The specified bucket does not exist.\",\n \"errors\": [\n {\n \"message\": \"The specified bucket does not exist.\",\n \"domain\": \"global\",\n \"reason\": \"notFound\"\n }\n ]\n }\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", + "X-GUploader-UploadID" : "AHVrFxNvP2UKqbCYITI33ZphRBInUDqDf9b0Inz9mGLxjJs0WcCswObEfeJV70ZeRny5nBNCAL_F900", + "Vary" : [ "Origin", "X-Origin" ], + "Pragma" : "no-cache", + "Expires" : "Mon, 01 Jan 1990 00:00:00 GMT", + "Date" : "Thu, 11 Dec 2025 02:40:41 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "ed5c97d1-8c90-4db2-ae7c-8fee37f52640", + "persistent" : true, + "insertionIndex" : 801 +} \ No newline at end of file diff --git a/blob/blob-gcp/src/test/resources/mappings/get-pdnihrvmgo.json b/blob/blob-gcp/src/test/resources/mappings/get-pdnihrvmgo.json new file mode 100644 index 000000000..46e8d6f86 --- /dev/null +++ b/blob/blob-gcp/src/test/resources/mappings/get-pdnihrvmgo.json @@ -0,0 +1,26 @@ +{ + "id" : "801ed53c-64a4-47dc-81f2-068b04e2c42a", + "name" : "storage_v1_b_substrate-sdk-gcp-poc1-test-bucket", + "request" : { + "url" : "/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket?projection=full", + "method" : "GET" + }, + "response" : { + "status" : 200, + "body" : "{\n \"kind\": \"storage#bucket\",\n \"selfLink\": \"https://www.googleapis.com/storage/v1/b/substrate-sdk-gcp-poc1-test-bucket\",\n \"id\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"name\": \"substrate-sdk-gcp-poc1-test-bucket\",\n \"projectNumber\": \"599653580068\",\n \"generation\": \"1758831398412595155\",\n \"metageneration\": \"1\",\n \"location\": \"US\",\n \"storageClass\": \"STANDARD\",\n \"etag\": \"CAE=\",\n \"timeCreated\": \"2025-09-25T20:16:38.678Z\",\n \"updated\": \"2025-09-25T20:16:38.678Z\",\n \"softDeletePolicy\": {\n \"retentionDurationSeconds\": \"604800\",\n \"effectiveTime\": \"2025-09-25T20:16:38.678Z\"\n },\n \"iamConfiguration\": {\n \"bucketPolicyOnly\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"uniformBucketLevelAccess\": {\n \"enabled\": true,\n \"lockedTime\": \"2025-12-24T20:16:38.678Z\"\n },\n \"publicAccessPrevention\": \"inherited\"\n },\n \"locationType\": \"multi-region\",\n \"rpo\": \"DEFAULT\"\n}\n", + "headers" : { + "Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000", + "Server" : "UploadServer", + "Cache-Control" : "private, max-age=0, must-revalidate, no-transform", + "ETag" : "CAE=", + "X-GUploader-UploadID" : "AHVrFxPFIPczXUuYjbLTOS5OI1mjvBLOLo0cSty4DeaopWomAFMA_wDwaAkmO71DuQqoR36S", + "Vary" : [ "Origin", "X-Origin" ], + "Expires" : "Thu, 11 Dec 2025 02:40:40 GMT", + "Date" : "Thu, 11 Dec 2025 02:40:40 GMT", + "Content-Type" : "application/json; charset=UTF-8" + } + }, + "uuid" : "801ed53c-64a4-47dc-81f2-068b04e2c42a", + "persistent" : true, + "insertionIndex" : 799 +} \ No newline at end of file