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)));
+ }
+ }
}