This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


The following commit(s) were added to refs/heads/main by this push:
     new 5bf85cb236 Add integration tests for blob snapshot, tags, find by tags 
and versions
5bf85cb236 is described below

commit 5bf85cb2361b96aa009e4c85cf3d9a99995cab22
Author: JinyuChen97 <[email protected]>
AuthorDate: Mon Jul 6 14:54:28 2026 +0100

    Add integration tests for blob snapshot, tags, find by tags and versions
    
    Add tests covering the following Azure Blob Storage operations in Camel 
Quarkus:
    - Blob index tags: set and get tags on a blob (setBlobTags / getBlobTags)
    - Find blobs by index tags: query blobs using a tag filter expression 
(findBlobsByTags)
    - Blob version ID access: list blob versions and read a specific historical 
version
    - Blob snapshot creation and retrieval: create a snapshot and read its 
content
    
    All new tests have been verified against both Azurite (local emulator)
    and the real Azure service.
    
    Co-authored-by: Bob <[email protected]>
---
 .../storage/blob/it/AzureStorageBlobResource.java  | 113 ++++++++++-
 .../storage/blob/it/AzureStorageBlobRoutes.java    |  26 +++
 .../storage/blob/it/AzureStorageBlobTest.java      | 226 +++++++++++++++++++++
 3 files changed, 363 insertions(+), 2 deletions(-)

diff --git 
a/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobResource.java
 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobResource.java
index 156af66d5d..db2741593e 100644
--- 
a/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobResource.java
+++ 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobResource.java
@@ -44,8 +44,10 @@ import com.azure.storage.blob.models.BlobStorageException;
 import com.azure.storage.blob.models.Block;
 import com.azure.storage.blob.models.BlockList;
 import com.azure.storage.blob.models.BlockListType;
+import com.azure.storage.blob.models.DeleteSnapshotsOptionType;
 import com.azure.storage.blob.models.PageRange;
 import com.azure.storage.blob.models.PageRangeItem;
+import com.azure.storage.blob.models.TaggedBlobItem;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import jakarta.json.Json;
@@ -172,9 +174,13 @@ public class AzureStorageBlobResource {
 
     @Path("/blob/delete")
     @DELETE
-    public Response deleteBlob() {
+    public Response deleteBlob(@QueryParam("deleteSnapshots") String 
deleteSnapshots) {
         try {
-            producerTemplate.sendBody("direct:delete", null);
+            Map<String, Object> headers = new HashMap<>();
+            if (deleteSnapshots != null) {
+                headers.put(BlobConstants.DELETE_SNAPSHOT_OPTION_TYPE, 
DeleteSnapshotsOptionType.fromString(deleteSnapshots));
+            }
+            producerTemplate.sendBodyAndHeaders("direct:delete", null, 
headers);
         } catch (CamelExecutionException e) {
             Throwable cause = e.getCause();
             if (cause instanceof BlobStorageException) {
@@ -447,6 +453,109 @@ public class AzureStorageBlobResource {
         return reflectiveInvoker != null;
     }
 
+    @Path("/blob/snapshot/create")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response createBlobSnapshot() throws URISyntaxException {
+        String snapshotId = 
producerTemplate.requestBody("direct:createBlobSnapshot", null, String.class);
+        return Response.created(new URI("https://camel.apache.org/";))
+                .entity(snapshotId)
+                .build();
+    }
+
+    @Path("/blob/snapshot/read")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String readBlobSnapshot(@QueryParam("snapshotId") String 
snapshotId) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(Exchange.CHARSET_NAME, StandardCharsets.UTF_8.name());
+        headers.put(BlobConstants.BLOB_SNAPSHOT_ID, snapshotId);
+        return 
producerTemplate.requestBodyAndHeaders("direct:readBlobSnapshot", null, 
headers, String.class);
+    }
+
+    @Path("/blob/tags/set")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response setBlobTags(Map<String, String> tags) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(BlobConstants.BLOB_TAGS, tags);
+        producerTemplate.sendBodyAndHeaders("direct:setBlobTags", null, 
headers);
+        return Response.ok().build();
+    }
+
+    @Path("/blob/tags/get")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @SuppressWarnings("unchecked")
+    public Map<String, String> getBlobTags() {
+        return producerTemplate.requestBody("direct:getBlobTags", null, 
Map.class);
+    }
+
+    @Path("/blob/tags/find")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @SuppressWarnings("unchecked")
+    public JsonObject findBlobsByTags(@QueryParam("filter") String filter) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(BlobConstants.BLOB_TAG_FILTER, filter);
+
+        List<?> blobs = 
producerTemplate.requestBodyAndHeaders("direct:findBlobsByTags", null, headers, 
List.class);
+
+        JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+        blobs.forEach(blob -> {
+            JsonObjectBuilder blobBuilder = Json.createObjectBuilder();
+            // The blob object has containerName and name properties
+            if (blob instanceof TaggedBlobItem) {
+                TaggedBlobItem taggedBlob = (TaggedBlobItem) blob;
+                blobBuilder.add("containerName", 
taggedBlob.getContainerName());
+                blobBuilder.add("name", taggedBlob.getName());
+            }
+            arrayBuilder.add(blobBuilder.build());
+        });
+
+        objectBuilder.add("blobs", arrayBuilder);
+        return objectBuilder.build();
+    }
+
+    @Path("/blob/versions/list")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @SuppressWarnings("unchecked")
+    public JsonObject listBlobVersions() {
+        List<?> versions = 
producerTemplate.requestBody("direct:listBlobVersions", null, List.class);
+
+        JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+        versions.forEach(version -> {
+            JsonObjectBuilder versionBuilder = Json.createObjectBuilder();
+            if (version instanceof BlobItem) {
+                BlobItem blobItem = (BlobItem) version;
+                versionBuilder.add("name", blobItem.getName());
+                if (blobItem.getVersionId() != null) {
+                    versionBuilder.add("versionId", blobItem.getVersionId());
+                }
+                versionBuilder.add("isCurrentVersion", 
blobItem.isCurrentVersion() != null && blobItem.isCurrentVersion());
+            }
+            arrayBuilder.add(versionBuilder.build());
+        });
+
+        objectBuilder.add("versions", arrayBuilder);
+        return objectBuilder.build();
+    }
+
+    @Path("/blob/version/read")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String readBlobVersion(@QueryParam("versionId") String versionId) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(Exchange.CHARSET_NAME, StandardCharsets.UTF_8.name());
+        headers.put(BlobConstants.BLOB_VERSION_ID, versionId);
+        return 
producerTemplate.requestBodyAndHeaders("direct:readBlobVersion", null, headers, 
String.class);
+    }
+
     private void extractBlockNames(JsonObjectBuilder builder, List<Block> 
blocks, BlockListType listType) {
         JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
         blocks.stream().map(Block::getName).forEach(arrayBuilder::add);
diff --git 
a/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobRoutes.java
 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobRoutes.java
index 4b872f0326..5d96bd6358 100644
--- 
a/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobRoutes.java
+++ 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobRoutes.java
@@ -133,6 +133,32 @@ public class AzureStorageBlobRoutes extends RouteBuilder {
 
         from("direct:deleteBlobContainer")
                 
.to(componentUri(BlobOperationsDefinition.deleteBlobContainer));
+
+        from("direct:createBlobSnapshot")
+                .to(componentUri(BlobOperationsDefinition.createBlobSnapshot));
+
+        from("direct:readBlobSnapshot")
+                .to(componentUri(BlobOperationsDefinition.getBlob));
+
+        from("direct:setBlobTags")
+                .to(componentUri(BlobOperationsDefinition.setBlobTags));
+
+        from("direct:getBlobTags")
+                .to(componentUri(BlobOperationsDefinition.getBlobTags));
+
+        from("direct:findBlobsByTags")
+                .toF("azure-storage-blob://%s?operation=%s",
+                        azureStorageAccountName,
+                        BlobOperationsDefinition.findBlobsByTags.name());
+
+        from("direct:listBlobVersions")
+                .toF("azure-storage-blob://%s/%s?operation=%s",
+                        azureStorageAccountName,
+                        azureBlobContainerName,
+                        BlobOperationsDefinition.listBlobVersions.name());
+
+        from("direct:readBlobVersion")
+                .to(componentUri(BlobOperationsDefinition.getBlob));
     }
 
     private String componentUri(final BlobOperationsDefinition operation) {
diff --git 
a/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java
 
b/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java
index 215144ae0e..c958361dc8 100644
--- 
a/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java
+++ 
b/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java
@@ -23,7 +23,9 @@ import java.nio.file.Paths;
 import java.time.OffsetDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -31,6 +33,7 @@ import com.azure.storage.blob.models.BlockListType;
 import io.quarkus.test.common.QuarkusTestResource;
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
+import io.restassured.common.mapper.TypeRef;
 import io.restassured.http.ContentType;
 import io.restassured.path.json.JsonPath;
 import 
org.apache.camel.quarkus.component.azure.storage.blob.it.AzureStorageHelper.ClientCertificateAuthEnabled;
@@ -607,4 +610,227 @@ class AzureStorageBlobTest {
                 .statusCode(200)
                 .body(is("true"));
     }
+
+    @Test
+    public void blobSnapshot() {
+        try {
+            String originalContent = "Original Content";
+            String updatedContent = "Updated Content";
+
+            // Create blob
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(originalContent)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Create snapshot
+            String snapshotId = 
RestAssured.post("/azure-storage-blob/blob/snapshot/create")
+                    .then()
+                    .statusCode(201)
+                    .extract()
+                    .body()
+                    .asString();
+
+            assertNotNull(snapshotId);
+
+            // Update blob content
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(updatedContent)
+                    .patch("/azure-storage-blob/blob/update")
+                    .then()
+                    .statusCode(200);
+
+            // Read current blob (should return updated content)
+            RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(updatedContent));
+
+            // Read snapshot (should return original content)
+            RestAssured.given()
+                    .queryParam("snapshotId", snapshotId)
+                    .get("/azure-storage-blob/blob/snapshot/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(originalContent));
+        } finally {
+            // Delete blob and all snapshots
+            RestAssured.given()
+                    .queryParam("deleteSnapshots", "include")
+                    .delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(anyOf(is(204), is(404)));
+        }
+    }
+
+    @Test
+    public void blobTags() {
+        try {
+            // Create blob
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body("Test content for tags")
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Set blob tags
+            Map<String, String> tags = new HashMap<>();
+            tags.put("environment", "test");
+            tags.put("project", "camel-quarkus");
+            tags.put("version", "1.0");
+
+            RestAssured.given()
+                    .contentType(ContentType.JSON)
+                    .body(tags)
+                    .post("/azure-storage-blob/blob/tags/set")
+                    .then()
+                    .statusCode(200);
+
+            // Get blob tags
+            Map<String, String> retrievedTags = 
RestAssured.get("/azure-storage-blob/blob/tags/get")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .as(new TypeRef<Map<String, String>>() {
+                    });
+
+            // Verify tags
+            assertEquals("test", retrievedTags.get("environment"));
+            assertEquals("camel-quarkus", retrievedTags.get("project"));
+            assertEquals("1.0", retrievedTags.get("version"));
+        } finally {
+            // Delete blob
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(anyOf(is(204), is(404)));
+        }
+    }
+
+    @Test
+    public void findBlobsByTags() {
+        String blobName1 = AzureStorageBlobRoutes.BLOB_NAME;
+
+        try {
+            // Create first blob with tags
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body("Content for blob 1")
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            Map<String, String> tags1 = new HashMap<>();
+            tags1.put("environment", "production");
+            tags1.put("team", "backend");
+
+            RestAssured.given()
+                    .contentType(ContentType.JSON)
+                    .body(tags1)
+                    .post("/azure-storage-blob/blob/tags/set")
+                    .then()
+                    .statusCode(200);
+
+            // On the real Azure service, blob index tag search is eventually 
consistent;
+            // poll until the result is available. Azurite returns results 
immediately.
+            String filter = "\"environment\" = 'production' AND \"team\" = 
'backend'";
+            Awaitility.await().pollInterval(5, TimeUnit.SECONDS).timeout(2, 
TimeUnit.MINUTES).until(() -> {
+                List<Map<String, String>> blobs = RestAssured.given()
+                        .queryParam("filter", filter)
+                        .get("/azure-storage-blob/blob/tags/find")
+                        .then()
+                        .statusCode(200)
+                        .extract()
+                        .jsonPath()
+                        .getList("blobs");
+                return blobs != null && blobs.size() == 1 && 
blobName1.equals(blobs.get(0).get("name"));
+            });
+        } finally {
+            // Delete blob
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(anyOf(is(204), is(404)));
+        }
+    }
+
+    // Blob versioning is not fully supported in Azurite
+    @EnabledIf({ MockBackendDisabled.class })
+    @Test
+    public void blobVersions() {
+        try {
+            String originalContent = "Version 1 Content";
+            String updatedContent = "Version 2 Content";
+
+            // Create blob (version 1)
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(originalContent)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Update blob (version 2)
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(updatedContent)
+                    .patch("/azure-storage-blob/blob/update")
+                    .then()
+                    .statusCode(200);
+
+            // List blob versions
+            List<Map<String, Object>> versions = 
RestAssured.get("/azure-storage-blob/blob/versions/list")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .jsonPath()
+                    .getList("versions");
+
+            // Verify we have at least 2 versions
+            assertEquals(2, versions.size(), "Should have 2 versions (original 
and updated)");
+
+            // Find the current version and an older version
+            Map<String, Object> currentVersion = versions.stream()
+                    .filter(v -> (Boolean) v.get("isCurrentVersion"))
+                    .findFirst()
+                    .orElse(null);
+
+            Map<String, Object> oldVersion = versions.stream()
+                    .filter(v -> !(Boolean) v.get("isCurrentVersion"))
+                    .findFirst()
+                    .orElse(null);
+
+            assertNotNull(currentVersion, "Should have a current version");
+            assertNotNull(oldVersion, "Should have an old version");
+
+            // Read current version (should return updated content)
+            String current = RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString();
+            assertEquals(updatedContent, current);
+
+            // Read old version (should return original content)
+            String oldVersionId = (String) oldVersion.get("versionId");
+            assertNotNull(oldVersionId, "Old version should have a versionId");
+            String old = RestAssured.given()
+                    .queryParam("versionId", oldVersionId)
+                    .get("/azure-storage-blob/blob/version/read")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString();
+            assertEquals(originalContent, old);
+        } finally {
+            // Delete blob
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(anyOf(is(204), is(404)));
+        }
+    }
 }

Reply via email to