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 22f6c47  Increase azure-storage-blob extension test coverage
22f6c47 is described below

commit 22f6c4703d37d86c2d17de8eac2349af1708e5b3
Author: James Netherton <[email protected]>
AuthorDate: Tue Feb 22 15:10:04 2022 +0000

    Increase azure-storage-blob extension test coverage
    
    Fixes #3561
---
 integration-test-groups/azure/README.adoc          |   3 +-
 .../azure/azure-eventhubs/pom.xml                  |   2 +-
 integration-test-groups/azure/azure-resources.sh   |   9 +-
 .../azure/azure-storage-blob/pom.xml               |  48 ++
 .../storage/blob/it/AzureStorageBlobProducers.java |  49 ++
 .../storage/blob/it/AzureStorageBlobResource.java  | 394 ++++++++++++++--
 .../storage/blob/it/AzureStorageBlobRoutes.java    | 125 ++++++
 .../storage/blob/it/AzureStorageBlobTest.java      | 500 +++++++++++++++++++--
 .../test/mock/backend/MockBackendDisabled.java     |  30 ++
 integration-tests/azure-grouped/pom.xml            |  41 +-
 10 files changed, 1111 insertions(+), 90 deletions(-)

diff --git a/integration-test-groups/azure/README.adoc 
b/integration-test-groups/azure/README.adoc
index 33c21ec..7115205 100644
--- a/integration-test-groups/azure/README.adoc
+++ b/integration-test-groups/azure/README.adoc
@@ -15,11 +15,12 @@ Prerequisites:
 
 * A 
https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&tabs=azure-portal[general-purpose
 v2 Azure storage account] and
 
https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal[create
 a container]
+* The 
https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-change-feed?tabs=azure-portal#enable-and-disable-the-change-feed[change
 feed] is enabled on your storage account
 * View the 
https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal#view-account-access-keys[account
 keys] and set the following environment variables:
 * An https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-create[Azure 
Event Hub]
 * An 
https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-get-connection-string[Event
 Hubs connection string]
 
-To create all of the above, you can use `azure-resources.sh` script as follows:
+To create all of the above, you can use `azure-resources.sh` script as 
follows. Ensure that you have installed the 
https://docs.microsoft.com/en-us/cli/azure/[Azure CLI] beforehand:
 
 [source,shell]
 ----
diff --git a/integration-test-groups/azure/azure-eventhubs/pom.xml 
b/integration-test-groups/azure/azure-eventhubs/pom.xml
index d382e49..67d83c3 100644
--- a/integration-test-groups/azure/azure-eventhubs/pom.xml
+++ b/integration-test-groups/azure/azure-eventhubs/pom.xml
@@ -49,7 +49,7 @@
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-resteasy-jackson</artifactId>
+            <artifactId>quarkus-resteasy-jsonb</artifactId>
         </dependency>
 
         <!-- test dependencies -->
diff --git a/integration-test-groups/azure/azure-resources.sh 
b/integration-test-groups/azure/azure-resources.sh
index 8b7a4b6..8bdcbaa 100755
--- a/integration-test-groups/azure/azure-resources.sh
+++ b/integration-test-groups/azure/azure-resources.sh
@@ -16,6 +16,13 @@
 # limitations under the License.
 #
 
+if ! which az > /dev/null 2>&1; then
+  echo "$(basename $0) requires the Azure CLI."
+  echo
+  echo "https://docs.microsoft.com/en-us/cli/azure/";
+  echo
+  exit 1
+fi
 
 suffix="$(az ad signed-in-user show --query displayName -o tsv | tr 
'[:upper:]' '[:lower:]' | tr -cd '[:alnum:]' | cut -c-12)"
 suffix="${suffix}4"
@@ -33,6 +40,7 @@ function createResources() {
     az group create --name ${RESOURCE_GROUP} --location ${ZONE}
 
     az storage account create --name ${AZURE_STORAGE_ACCOUNT_NAME} 
--resource-group ${RESOURCE_GROUP} --location ${ZONE} --sku Standard_LRS --kind 
StorageV2
+    az storage account blob-service-properties update --enable-change-feed 
true --delete-retention-days 1 -n ${AZURE_STORAGE_ACCOUNT_NAME} -g 
${RESOURCE_GROUP}
 
     SUBSCRIPTION_ID="$(az account list --query '[0].id' -o tsv)"
     USER_ID="$(az ad signed-in-user show --query objectId -o tsv)"
@@ -79,4 +87,3 @@ delete)  echo "Deleting Azure resources"
 *) echo "usage: $0 [create|delete]"
    ;;
 esac
-
diff --git a/integration-test-groups/azure/azure-storage-blob/pom.xml 
b/integration-test-groups/azure/azure-storage-blob/pom.xml
index 7da0161..23b579f 100644
--- a/integration-test-groups/azure/azure-storage-blob/pom.xml
+++ b/integration-test-groups/azure/azure-storage-blob/pom.xml
@@ -36,9 +36,21 @@
             <artifactId>camel-quarkus-azure-storage-blob</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-direct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-seda</artifactId>
+        </dependency>
+        <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-resteasy</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-jsonb</artifactId>
+        </dependency>
 
         <!-- test dependencies -->
         <dependency>
@@ -52,6 +64,16 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-test-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             
<artifactId>camel-quarkus-integration-tests-support-azure</artifactId>
             <scope>test</scope>
@@ -108,6 +130,32 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-direct-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-seda-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
             </dependencies>
         </profile>
         <profile>
diff --git 
a/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobProducers.java
 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobProducers.java
new file mode 100644
index 0000000..dfd65be
--- /dev/null
+++ 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobProducers.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.azure.storage.blob.it;
+
+import javax.inject.Named;
+
+import com.azure.core.http.policy.HttpLogDetailLevel;
+import com.azure.core.http.policy.HttpLogOptions;
+import com.azure.storage.blob.BlobServiceClient;
+import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+public class AzureStorageBlobProducers {
+
+    @ConfigProperty(name = "azure.storage.account-name")
+    String azureStorageAccountName;
+
+    @ConfigProperty(name = "azure.storage.account-key")
+    String azureStorageAccountKey;
+
+    @ConfigProperty(name = "azure.blob.service.url")
+    String azureBlobServiceUrl;
+
+    @Named("azureBlobServiceClient")
+    public BlobServiceClient createBlobClient() throws Exception {
+        StorageSharedKeyCredential credentials = new 
StorageSharedKeyCredential(azureStorageAccountName,
+                azureStorageAccountKey);
+        return new BlobServiceClientBuilder()
+                .endpoint(azureBlobServiceUrl)
+                .credential(credentials)
+                .httpLogOptions(new 
HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS).setPrettyPrintBody(true))
+                .buildClient();
+    }
+}
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 95e7bb0..790cbb1 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
@@ -16,105 +16,407 @@
  */
 package org.apache.camel.quarkus.component.azure.storage.blob.it;
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
-import javax.inject.Named;
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.PATCH;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
-import com.azure.core.http.policy.HttpLogDetailLevel;
-import com.azure.core.http.policy.HttpLogOptions;
-import com.azure.storage.blob.BlobServiceClient;
-import com.azure.storage.blob.BlobServiceClientBuilder;
-import com.azure.storage.common.StorageSharedKeyCredential;
+import com.azure.storage.blob.changefeed.models.BlobChangefeedEvent;
+import com.azure.storage.blob.changefeed.models.BlobChangefeedEventType;
+import com.azure.storage.blob.models.BlobContainerItem;
+import com.azure.storage.blob.models.BlobItem;
+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.PageList;
+import com.azure.storage.blob.models.PageRange;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.ConsumerTemplate;
 import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.Processor;
 import org.apache.camel.ProducerTemplate;
-import org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition;
+import org.apache.camel.component.azure.storage.blob.BlobBlock;
+import org.apache.camel.component.azure.storage.blob.BlobConstants;
+import org.apache.camel.quarkus.core.util.FileUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
 @Path("/azure-storage-blob")
 @ApplicationScoped
 public class AzureStorageBlobResource {
-    private static final String BLOB_NAME = "test";
 
     @Inject
     ProducerTemplate producerTemplate;
 
-    @ConfigProperty(name = "azure.storage.account-name")
-    String azureStorageAccountName;
+    @Inject
+    ConsumerTemplate consumerTemplate;
 
-    @ConfigProperty(name = "azure.storage.account-key")
-    String azureStorageAccountKey;
+    @Inject
+    CamelContext context;
 
-    @ConfigProperty(name = "azure.blob.service.url")
-    String azureBlobServiceUrl;
+    @ConfigProperty(name = "azure.storage.account-name")
+    public String azureStorageAccountName;
 
     @ConfigProperty(name = "azure.blob.container.name")
-    String azureBlobContainerName;
-
-    @javax.enterprise.inject.Produces
-    @Named("azureBlobServiceClient")
-    public BlobServiceClient createBlobClient() throws Exception {
-        StorageSharedKeyCredential credentials = new 
StorageSharedKeyCredential(azureStorageAccountName,
-                azureStorageAccountKey);
-        return new BlobServiceClientBuilder()
-                .endpoint(azureBlobServiceUrl)
-                .credential(credentials)
-                .httpLogOptions(new 
HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS).setPrettyPrintBody(true))
-                .buildClient();
-    }
+    public String azureBlobContainerName;
 
     @Path("/blob/create")
     @POST
     @Consumes(MediaType.TEXT_PLAIN)
-    public Response createBlob(String message) throws Exception {
-        
producerTemplate.sendBody(componentUri(BlobOperationsDefinition.uploadBlockBlob),
-                message);
-        return Response.created(new URI("https://camel.apache.org/";)).build();
+    public Response createBlob(String content) throws Exception {
+        Exchange exchange = producerTemplate.request("direct:create", new 
Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getMessage().setBody(content);
+            }
+        });
+
+        if (!exchange.isFailed()) {
+            Message message = exchange.getMessage();
+            return Response.created(new URI("https://camel.apache.org/";))
+                    .entity(message.getHeader(BlobConstants.E_TAG))
+                    .build();
+        }
+
+        return Response.serverError().build();
     }
 
     @Path("/blob/read")
     @GET
     @Produces(MediaType.TEXT_PLAIN)
-    public String readBlob() throws Exception {
+    public String readBlob(@QueryParam("containerName") String containerName) {
+        if (containerName == null) {
+            containerName = azureBlobContainerName;
+        }
+
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(Exchange.CHARSET_NAME, StandardCharsets.UTF_8.name());
+        headers.put(BlobConstants.BLOB_CONTAINER_NAME, containerName);
+        return producerTemplate.requestBodyAndHeaders("direct:read", null, 
headers, String.class);
+    }
+
+    @Path("/blob/read/bytes")
+    @GET
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    public byte[] readBlobBytes() {
         return producerTemplate.requestBodyAndHeader(
-                componentUri(BlobOperationsDefinition.getBlob),
-                null, Exchange.CHARSET_NAME, StandardCharsets.UTF_8.name(), 
String.class);
+                "direct:read",
+                null, Exchange.CHARSET_NAME, StandardCharsets.UTF_8.name(), 
byte[].class);
+    }
+
+    @Path("/blob/list")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @SuppressWarnings("unchecked")
+    public JsonObject listBlobs() {
+        JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+        List<BlobItem> blobs = producerTemplate.requestBody("direct:list", 
null, List.class);
+        blobs.stream()
+                .map(blobItem -> objectBuilder.add("name", blobItem.getName()))
+                .forEach(arrayBuilder::add);
+
+        objectBuilder.add("blobs", arrayBuilder);
+
+        return objectBuilder.build();
     }
 
     @Path("/blob/update")
     @PATCH
     @Consumes(MediaType.TEXT_PLAIN)
-    public Response updateBlob(String message) throws Exception {
-        producerTemplate.sendBody(
-                componentUri(BlobOperationsDefinition.uploadBlockBlob),
-                message);
+    public Response updateBlob(String message) {
+        producerTemplate.sendBody("direct:update", message);
         return Response.ok().build();
     }
 
     @Path("/blob/delete")
     @DELETE
-    public Response deleteBlob() throws Exception {
-        producerTemplate.sendBody(
-                componentUri(BlobOperationsDefinition.deleteBlob),
-                null);
+    public Response deleteBlob() {
+        try {
+            producerTemplate.sendBody("direct:delete", null);
+        } catch (CamelExecutionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof BlobStorageException) {
+                BlobStorageException bse = (BlobStorageException) cause;
+                return Response.status(bse.getStatusCode()).build();
+            }
+        }
         return Response.noContent().build();
     }
 
-    private String componentUri(final BlobOperationsDefinition operation) {
-        return 
String.format("azure-storage-blob://%s/%s?blobServiceClient=#azureBlobServiceClient&operation=%s&blobName=%s",
-                azureStorageAccountName, azureBlobContainerName,
-                operation.name(), BLOB_NAME);
+    @Path("/blob/download")
+    @GET
+    public Response downloadBlob() {
+        File file = producerTemplate.requestBody("direct:download", null, 
File.class);
+        String downloadPath = FileUtils.nixifyPath(file.getAbsolutePath());
+        return Response.ok(downloadPath).build();
+    }
+
+    @Path("/blob/download/link")
+    @GET
+    public Response downloadLink() {
+        String link = producerTemplate.requestBody("direct:downloadLink", 
null, String.class);
+        return Response.ok(link).build();
+    }
+
+    @Path("/block/blob/create")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public Response createBlockBlob(String content) throws Exception {
+        producerTemplate.sendBody("direct:uploadBlockBlob", content);
+        return Response.created(new URI("https://camel.apache.org/";)).build();
+    }
+
+    /**
+     * Note: The 'blob block' naming is retained here instead of the 
alternative 'block blob' naming used
+     * for other operations. Both the Camel and official Azure documentation 
have this inconsistency.
+     */
+    @Path("/blob/block/list")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public JsonObject readBlobBlockList(@QueryParam("blockListType") String 
blockListType) {
+        BlockListType listType = 
BlockListType.valueOf(blockListType.toUpperCase());
+        BlockList list = producerTemplate.requestBodyAndHeader(
+                "direct:readBlobBlocks",
+                null, BlobConstants.BLOCK_LIST_TYPE, listType, 
BlockList.class);
+
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        if (listType.equals(BlockListType.ALL) || 
listType.equals(BlockListType.UNCOMMITTED)) {
+            extractBlockNames(builder, list.getUncommittedBlocks(), 
BlockListType.UNCOMMITTED);
+        }
+
+        if (listType.equals(BlockListType.ALL) || 
listType.equals(BlockListType.COMMITTED)) {
+            extractBlockNames(builder, list.getCommittedBlocks(), 
BlockListType.COMMITTED);
+        }
+
+        return builder.build();
+    }
+
+    @Path("/block/blob/stage")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Boolean stageBlockBlobs(List<String> blockContent) {
+        List<BlobBlock> blocks = blockContent.stream()
+                .map(String::getBytes)
+                .map(ByteArrayInputStream::new)
+                .map(inputStream -> {
+                    try {
+                        return BlobBlock.createBlobBlock(inputStream);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                })
+                .collect(Collectors.toList());
+        return producerTemplate.requestBody("direct:stageBlockBlob", blocks, 
Boolean.class);
+    }
+
+    @Path("/block/blob/commit")
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Boolean commitBlockBlobs(List<String> blockNames) {
+        List<Block> blocks = blockNames.stream()
+                .map(name -> {
+                    Block block = new Block();
+                    block.setName(name);
+                    return block;
+                })
+                .collect(Collectors.toList());
+
+        return producerTemplate.requestBody("direct:commitBlockBlob", blocks, 
Boolean.class);
+    }
+
+    @Path("/append/blob/create")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public Response createAppendBlob(String content) throws URISyntaxException 
{
+        producerTemplate.sendBody("direct:createAppendBlob", content);
+        return Response.created(new URI("https://camel.apache.org/";)).build();
+    }
+
+    @Path("/append/blob/commit")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean commitAppendBlob(String contentToAppend) {
+        byte[] bytes = contentToAppend.getBytes(StandardCharsets.UTF_8);
+        return producerTemplate.requestBody("direct:commitAppendBlob", new 
ByteArrayInputStream(bytes), Boolean.class);
+    }
+
+    @Path("/page/blob/create")
+    @POST
+    public Response createPageBlob() throws URISyntaxException {
+        producerTemplate.sendBody("direct:createPageBlob", null);
+        return Response.created(new URI("https://camel.apache.org/";)).build();
+    }
+
+    @Path("/page/blob/upload")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean uploadPageBlob(@QueryParam("pageStart") int start, 
@QueryParam("pageEnd") int end) {
+        byte[] dataBytes = new byte[end + 1];
+        new Random().nextBytes(dataBytes);
+        InputStream dataStream = new ByteArrayInputStream(dataBytes);
+        PageRange pageRange = new PageRange().setStart(start).setEnd(end);
+        return producerTemplate.requestBodyAndHeader("direct:uploadPageBlob", 
dataStream,
+                BlobConstants.PAGE_BLOB_RANGE, pageRange, Boolean.class);
+    }
+
+    @Path("/page/blob/resize")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean resizePageBlob(@QueryParam("pageStart") int start, 
@QueryParam("pageEnd") int end) {
+        PageRange pageRange = new PageRange().setStart(start).setEnd(end);
+        return producerTemplate.requestBodyAndHeader("direct:resizePageBlob", 
null,
+                BlobConstants.PAGE_BLOB_RANGE, pageRange, Boolean.class);
+    }
+
+    @Path("/page/blob/clear")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean clearPageBlob(@QueryParam("pageStart") int start, 
@QueryParam("pageEnd") int end) {
+        PageRange pageRange = new PageRange().setStart(start).setEnd(end);
+        return producerTemplate.requestBodyAndHeader("direct:clearPageBlob", 
null,
+                BlobConstants.PAGE_BLOB_RANGE, pageRange, Boolean.class);
+    }
+
+    @Path("/page/blob")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public JsonObject getPageBlobRanges(@QueryParam("pageStart") int start, 
@QueryParam("pageEnd") int end) {
+        PageRange pageRange = new PageRange().setStart(start).setEnd(end);
+        PageList pageList = 
producerTemplate.requestBodyAndHeader("direct:getPageBlobRanges", null,
+                BlobConstants.PAGE_BLOB_RANGE, pageRange, PageList.class);
+
+        JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+        pageList.getPageRange()
+                .stream()
+                .map(pr -> Json.createObjectBuilder()
+                        .add("start", pr.getStart())
+                        .add("end", pr.getEnd())
+                        .build())
+                .forEach(arrayBuilder::add);
+
+        objectBuilder.add("ranges", arrayBuilder.build());
+        return objectBuilder.build();
+    }
+
+    @Path("/blob/container")
+    @POST
+    public Response createBlobContainer(@QueryParam("containerName") String 
containerName) throws Exception {
+        producerTemplate.sendBodyAndHeader("direct:createBlobContainer", null, 
BlobConstants.BLOB_CONTAINER_NAME,
+                containerName);
+        return Response.created(new URI("https://camel.apache.org/";)).build();
+    }
+
+    @Path("/blob/container")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @SuppressWarnings("unchecked")
+    public JsonObject listBlobContainers() throws Exception {
+        JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+        List<BlobContainerItem> containers = 
producerTemplate.requestBody("direct:listBlobContainers", null, List.class);
+        containers.stream()
+                .map(container -> Json.createObjectBuilder()
+                        .add("name", container.getName())
+                        .build())
+                .forEach(arrayBuilder::add);
+
+        objectBuilder.add("containers", arrayBuilder.build());
+        return objectBuilder.build();
     }
 
+    @Path("/blob/container")
+    @DELETE
+    public void deleteBlobContainer(@QueryParam("containerName") String 
containerName) {
+        producerTemplate.sendBodyAndHeader("direct:deleteBlobContainer", null, 
BlobConstants.BLOB_CONTAINER_NAME,
+                containerName);
+    }
+
+    @Path("/blob/copy")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response copyBlob(@QueryParam("containerName") String 
containerName) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(BlobConstants.BLOB_CONTAINER_NAME, containerName);
+        headers.put(BlobConstants.BLOB_NAME, AzureStorageBlobRoutes.BLOB_NAME);
+        headers.put(BlobConstants.SOURCE_BLOB_CONTAINER_NAME, 
azureBlobContainerName);
+        headers.put(BlobConstants.SOURCE_BLOB_ACCOUNT_NAME, 
azureStorageAccountName);
+        String result = producerTemplate.requestBodyAndHeaders("direct:copy", 
null, headers, String.class);
+        return Response.ok(result).build();
+    }
+
+    @Path("/changes")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    @SuppressWarnings("unchecked")
+    public boolean getChangeFeed(
+            @QueryParam("startTime") String startTime,
+            @QueryParam("endTime") String endTime,
+            @QueryParam("etag") String eTag) {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(BlobConstants.BLOB_NAME, AzureStorageBlobRoutes.BLOB_NAME);
+        headers.put(BlobConstants.CHANGE_FEED_START_TIME, 
OffsetDateTime.parse(startTime));
+        headers.put(BlobConstants.CHANGE_FEED_END_TIME, 
OffsetDateTime.parse(endTime));
+
+        List<BlobChangefeedEvent> events = 
producerTemplate.requestBodyAndHeaders("direct:getChangeFeed", null, headers,
+                List.class);
+        return events.stream()
+                .filter(event -> 
event.getEventType().equals(BlobChangefeedEventType.BLOB_CREATED))
+                .anyMatch(event -> event.getData().getETag().equals(eTag));
+    }
+
+    @Path("/consumed/blobs")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String getConsumedBlobs() {
+        return consumerTemplate.receiveBody("seda:blobs", 10000, String.class);
+    }
+
+    @POST
+    @Path("consumer/{enable}")
+    public void mangeBlobConsumer(@PathParam("enable") boolean enable) throws 
Exception {
+        if (enable) {
+            context.getRouteController().startRoute("blob-consumer");
+        } else {
+            context.getRouteController().stopRoute("blob-consumer");
+        }
+    }
+
+    private void extractBlockNames(JsonObjectBuilder builder, List<Block> 
blocks, BlockListType listType) {
+        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+        blocks.stream().map(Block::getName).forEach(arrayBuilder::add);
+        builder.add(listType.toString(), arrayBuilder);
+    }
 }
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
new file mode 100644
index 0000000..a1de8d5
--- /dev/null
+++ 
b/integration-test-groups/azure/azure-storage-blob/src/main/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobRoutes.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.azure.storage.blob.it;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.azure.storage.blob.BlobConstants;
+import org.apache.camel.component.azure.storage.blob.BlobOperationsDefinition;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+@ApplicationScoped
+public class AzureStorageBlobRoutes extends RouteBuilder {
+
+    public static final String BLOB_NAME = "test";
+
+    @ConfigProperty(name = "azure.storage.account-name")
+    public String azureStorageAccountName;
+
+    @ConfigProperty(name = "azure.blob.container.name")
+    public String azureBlobContainerName;
+
+    @ConfigProperty(name = "azure.storage.account-key")
+    public String azureStorageAccountKey;
+
+    @Override
+    public void configure() throws Exception {
+        fromF("azure-storage-blob://%s/%s", azureStorageAccountName, 
azureBlobContainerName)
+                .id("blob-consumer")
+                .autoStartup(false)
+                .to("seda:blobs");
+
+        from("direct:create")
+                .to(componentUri(BlobOperationsDefinition.uploadBlockBlob));
+
+        from("direct:read")
+                .to(componentUri(BlobOperationsDefinition.getBlob));
+
+        from("direct:update")
+                .to(componentUri(BlobOperationsDefinition.uploadBlockBlob));
+
+        from("direct:delete")
+                .to(componentUri(BlobOperationsDefinition.deleteBlob));
+
+        from("direct:list")
+                .to(componentUri(BlobOperationsDefinition.listBlobs));
+
+        from("direct:download")
+                .to(componentUri(BlobOperationsDefinition.downloadBlobToFile) 
+ "&fileDir=target");
+
+        from("direct:copy")
+                .to(componentUri(BlobOperationsDefinition.copyBlob) + 
"&sourceBlobAccessKey=RAW("
+                        + azureStorageAccountKey + ")");
+
+        from("direct:downloadLink")
+                .to(componentUri(BlobOperationsDefinition.downloadLink))
+                .setBody().header(BlobConstants.DOWNLOAD_LINK);
+
+        from("direct:uploadBlockBlob")
+                .to(componentUri(BlobOperationsDefinition.uploadBlockBlob));
+
+        from("direct:stageBlockBlob")
+                .to(componentUri(BlobOperationsDefinition.stageBlockBlobList));
+
+        from("direct:commitBlockBlob")
+                
.to(componentUri(BlobOperationsDefinition.commitBlobBlockList));
+
+        from("direct:readBlobBlocks")
+                .to(componentUri(BlobOperationsDefinition.getBlobBlockList));
+
+        from("direct:createAppendBlob")
+                .to(componentUri(BlobOperationsDefinition.createAppendBlob));
+
+        from("direct:commitAppendBlob")
+                .to(componentUri(BlobOperationsDefinition.commitAppendBlob));
+
+        from("direct:createPageBlob")
+                .to(componentUri(BlobOperationsDefinition.createPageBlob));
+
+        from("direct:uploadPageBlob")
+                .to(componentUri(BlobOperationsDefinition.uploadPageBlob));
+
+        from("direct:resizePageBlob")
+                .to(componentUri(BlobOperationsDefinition.resizePageBlob));
+
+        from("direct:clearPageBlob")
+                .to(componentUri(BlobOperationsDefinition.clearPageBlob));
+
+        from("direct:getPageBlobRanges")
+                .to(componentUri(BlobOperationsDefinition.getPageBlobRanges));
+
+        from("direct:getChangeFeed")
+                .toF(componentUri(BlobOperationsDefinition.getChangeFeed));
+
+        from("direct:createBlobContainer")
+                
.to(componentUri(BlobOperationsDefinition.createBlobContainer));
+
+        from("direct:listBlobContainers")
+                .to(componentUri(BlobOperationsDefinition.listBlobContainers));
+
+        from("direct:deleteBlobContainer")
+                
.to(componentUri(BlobOperationsDefinition.deleteBlobContainer));
+    }
+
+    private String componentUri(final BlobOperationsDefinition operation) {
+        return 
String.format("azure-storage-blob://%s/%s?operation=%s&blobName=%s",
+                azureStorageAccountName,
+                azureBlobContainerName,
+                operation.name(), BLOB_NAME);
+    }
+}
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 de63caf..6594824 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
@@ -16,40 +16,64 @@
  */
 package org.apache.camel.quarkus.component.azure.storage.blob.it;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.OffsetDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
 import com.azure.core.http.policy.HttpLogDetailLevel;
 import com.azure.core.http.policy.HttpLogOptions;
 import com.azure.storage.blob.BlobContainerClient;
 import com.azure.storage.blob.BlobServiceClient;
 import com.azure.storage.blob.BlobServiceClientBuilder;
+import com.azure.storage.blob.models.BlockListType;
 import com.azure.storage.common.StorageSharedKeyCredential;
 import io.quarkus.test.common.QuarkusTestResource;
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
 import io.restassured.http.ContentType;
+import io.restassured.path.json.JsonPath;
+import org.apache.camel.quarkus.test.EnabledIf;
+import org.apache.camel.quarkus.test.mock.backend.MockBackendDisabled;
 import org.apache.camel.quarkus.test.support.azure.AzureStorageTestResource;
 import org.eclipse.microprofile.config.Config;
 import org.eclipse.microprofile.config.ConfigProvider;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.org.awaitility.Awaitility;
 
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.hamcrest.text.MatchesPattern.matchesPattern;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
 
 @QuarkusTest
 @QuarkusTestResource(AzureStorageTestResource.class)
 class AzureStorageBlobTest {
 
+    private static final String BLOB_CONTENT = "Hello Camel Quarkus Azure 
Blob";
+
     @BeforeAll
     static void beforeAll() {
-        blobContainer().create();
+        getClient().create();
     }
 
     @AfterAll
     static void afterAll() {
-        blobContainer().delete();
+        getClient().delete();
     }
 
-    private static BlobContainerClient blobContainer() {
+    private static BlobContainerClient getClient() {
         final Config config = ConfigProvider.getConfig();
         final String azureStorageAccountName = 
config.getValue("azure.storage.account-name",
                 String.class);
@@ -63,47 +87,443 @@ class AzureStorageBlobTest {
                 .credential(credentials)
                 .httpLogOptions(new 
HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS).setPrettyPrintBody(true))
                 .buildClient();
-        BlobContainerClient blobContainer = client
-                
.getBlobContainerClient(config.getValue("azure.blob.container.name", 
String.class));
-        return blobContainer;
+
+        String containerName = config.getValue("azure.blob.container.name", 
String.class);
+        return client.getBlobContainerClient(containerName);
     }
 
     @Test
     public void crud() {
-        String blobContent = "Hello Camel Quarkus Azure Blob";
-
-        // Create
-        RestAssured.given()
-                .contentType(ContentType.TEXT)
-                .body(blobContent)
-                .post("/azure-storage-blob/blob/create")
-                .then()
-                .statusCode(201);
-
-        // Read
-        RestAssured.get("/azure-storage-blob/blob/read")
-                .then()
-                .statusCode(200)
-                .body(is(blobContent));
-
-        // Update
-        String updatedContent = blobContent + " updated";
-        RestAssured.given()
-                .contentType(ContentType.TEXT)
-                .body(updatedContent)
-                .patch("/azure-storage-blob/blob/update")
-                .then()
-                .statusCode(200);
-
-        RestAssured.get("/azure-storage-blob/blob/read")
-                .then()
-                .statusCode(200)
-                .body(is(updatedContent));
-
-        // Delete
-        RestAssured.delete("/azure-storage-blob/blob/delete")
-                .then()
-                .statusCode(204);
+        try {
+            // Create
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Read
+            RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(BLOB_CONTENT));
+
+            // List
+            RestAssured.get("/azure-storage-blob/blob/list")
+                    .then()
+                    .statusCode(200)
+                    .body("blobs[0].name", 
is(AzureStorageBlobRoutes.BLOB_NAME));
+
+            // Update
+            String updatedContent = BLOB_CONTENT + " updated";
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(updatedContent)
+                    .patch("/azure-storage-blob/blob/update")
+                    .then()
+                    .statusCode(200);
+
+            RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(updatedContent));
+        } finally {
+            // Delete
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void download() throws IOException {
+        Path path = null;
+        try {
+            // Create
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Download file
+            String downloadPath = 
RestAssured.get("/azure-storage-blob/blob/download")
+                    .then()
+                    .statusCode(200)
+                    .body(endsWith("target/test"))
+                    .extract()
+                    .body()
+                    .asString();
+
+            path = Paths.get(downloadPath);
+            assertEquals(BLOB_CONTENT, Files.readString(path));
+
+            // Download link
+            RestAssured.get("/azure-storage-blob/blob/download/link")
+                    .then()
+                    .statusCode(200)
+                    .body(matchesPattern("^(https?)://.*/test.*"));
+        } finally {
+            if (path != null) {
+                try {
+                    Files.deleteIfExists(path);
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+
+            // Delete
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void blockBlobStageCommit() {
+        try {
+            List<String> blockContent = Arrays.asList(BLOB_CONTENT.split(" "));
+
+            // Stage blocks
+            RestAssured.given()
+                    .contentType(ContentType.JSON)
+                    .body(blockContent)
+                    .post("/azure-storage-blob/block/blob/stage")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            // Verify blocks uncommitted
+            JsonPath json = RestAssured.given()
+                    .queryParam("blockListType", BlockListType.UNCOMMITTED)
+                    .get("/azure-storage-blob/blob/block/list")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .jsonPath();
+
+            List<String> uncommittedBlocks = 
json.getList(BlockListType.UNCOMMITTED.toString());
+            assertNotNull(uncommittedBlocks);
+            assertEquals(blockContent.size(), uncommittedBlocks.size());
+
+            // Commit blocks
+            RestAssured.given()
+                    .contentType(ContentType.JSON)
+                    .body(uncommittedBlocks)
+                    .post("/azure-storage-blob/block/blob/commit")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            // Verify blocks committed
+            json = RestAssured.given()
+                    .queryParam("blockListType", BlockListType.COMMITTED)
+                    .get("/azure-storage-blob/blob/block/list")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .jsonPath();
+
+            List<String> committedBlocks = 
json.getList(BlockListType.COMMITTED.toString());
+            assertNotNull(committedBlocks);
+            assertEquals(blockContent.size(), committedBlocks.size());
+        } finally {
+            // Delete
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void appendBlob() {
+        try {
+            // Create
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/append/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Commit
+            String appendedContent = BLOB_CONTENT + " Appended";
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(appendedContent)
+                    .post("/azure-storage-blob/append/blob/commit")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            // Read
+            RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(appendedContent));
+        } finally {
+            // Delete
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void pageBlob() {
+        try {
+            // Create
+            RestAssured.given()
+                    .post("/azure-storage-blob/page/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Upload
+            RestAssured.given()
+                    .queryParam("pageStart", 0)
+                    .queryParam("pageEnd", 511)
+                    .post("/azure-storage-blob/page/blob/upload")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            byte[] pageData = 
RestAssured.get("/azure-storage-blob/blob/read/bytes")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asByteArray();
+
+            assertEquals(512, pageData.length);
+
+            // Get ranges
+            RestAssured.given()
+                    .queryParam("pageStart", 0)
+                    .queryParam("pageEnd", 511)
+                    .get("/azure-storage-blob/page/blob")
+                    .then()
+                    .statusCode(200)
+                    .body("ranges[0].start", is(0),
+                            "ranges[0].end", is(511));
+
+            // Resize
+            RestAssured.given()
+                    .queryParam("pageStart", 0)
+                    .queryParam("pageEnd", 1023)
+                    .post("/azure-storage-blob/page/blob/resize")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            // Read after resize
+            pageData = RestAssured.get("/azure-storage-blob/blob/read/bytes")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asByteArray();
+
+            assertEquals(1024, pageData.length);
+
+            // Verify page data beyond the resized point is empty
+            for (int i = 512; i < pageData.length; i++) {
+                if (pageData[i] != 0) {
+                    fail("Expected byte element at position " + i + " to be 
zero value");
+                }
+            }
+
+            // Clear
+            RestAssured.given()
+                    .queryParam("pageStart", 0)
+                    .queryParam("pageEnd", 1023)
+                    .post("/azure-storage-blob/page/blob/clear")
+                    .then()
+                    .statusCode(200)
+                    .body(is("true"));
+
+            // Read after clear
+            pageData = RestAssured.get("/azure-storage-blob/blob/read/bytes")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asByteArray();
+
+            // Verify all page data is empty
+            for (int i = 0; i < pageData.length; i++) {
+                if (pageData[i] != 0) {
+                    fail("Expected byte element at position " + i + " to be 
zero value");
+                }
+            }
+        } finally {
+            // Delete
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
     }
 
+    @Test
+    public void blobContainer() {
+        String alternativeContainerName = "cq-test-" + UUID.randomUUID();
+
+        try {
+            // Create
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .post("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(201);
+
+            // List
+            String containerName = 
ConfigProvider.getConfig().getValue("azure.blob.container.name", String.class);
+            RestAssured.get("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(200)
+                    .body("containers.name",
+                            containsInAnyOrder(containerName, 
alternativeContainerName));
+        } finally {
+            // Delete
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .delete("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void copyBlob() {
+        String alternativeContainerName = "cq-test-" + UUID.randomUUID();
+
+        try {
+            // Create container to copy to
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .post("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(201);
+
+            // List
+            String containerName = 
ConfigProvider.getConfig().getValue("azure.blob.container.name", String.class);
+            RestAssured.get("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(200)
+                    .body("containers.name",
+                            containsInAnyOrder(containerName, 
alternativeContainerName));
+
+            // Create blob in first container
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Read
+            RestAssured.get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(BLOB_CONTENT));
+
+            // Copy blob to alternate storage container
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .post("/azure-storage-blob/blob/copy")
+                    .then()
+                    .statusCode(200);
+
+            // Read blob from alternate storage container
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .get("/azure-storage-blob/blob/read")
+                    .then()
+                    .statusCode(200)
+                    .body(is(BLOB_CONTENT));
+        } finally {
+            // Delete
+            RestAssured.given()
+                    .queryParam("containerName", alternativeContainerName)
+                    .delete("/azure-storage-blob/blob/container")
+                    .then()
+                    .statusCode(204);
+
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @Test
+    public void blobConsumer() {
+        try {
+            // Start blob consumer
+            RestAssured.given()
+                    .post("/azure-storage-blob/consumer/true")
+                    .then()
+                    .statusCode(204);
+
+            // Create blob
+            RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201);
+
+            // Fetch results
+            RestAssured.get("/azure-storage-blob/consumed/blobs")
+                    .then()
+                    .statusCode(200)
+                    .body(is(BLOB_CONTENT));
+        } finally {
+            // Stop blob consumer
+            RestAssured.given()
+                    .post("/azure-storage-blob/consumer/false")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    // Change feed is not available in Azurite
+    @EnabledIf({ MockBackendDisabled.class })
+    @Test
+    public void changeFeed() {
+        try {
+            String eTag = RestAssured.given()
+                    .contentType(ContentType.TEXT)
+                    .body(BLOB_CONTENT)
+                    .post("/azure-storage-blob/blob/create")
+                    .then()
+                    .statusCode(201)
+                    .extract()
+                    .body()
+                    .asString();
+
+            OffsetDateTime now = OffsetDateTime.now();
+            OffsetDateTime startTime = now.minus(5, ChronoUnit.MINUTES);
+            OffsetDateTime endTime = now.plus(5, ChronoUnit.MINUTES);
+
+            // Poll change feed until the blob just created is present
+            Awaitility.await().pollInterval(5, TimeUnit.SECONDS).timeout(5, 
TimeUnit.MINUTES).until(() -> RestAssured.given()
+                    .queryParam("startTime", startTime.toString())
+                    .queryParam("endTime", endTime.toString())
+                    .queryParam("etag", eTag)
+                    .get("/azure-storage-blob/changes")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString()
+                    .equals("true"));
+        } finally {
+            RestAssured.delete("/azure-storage-blob/blob/delete")
+                    .then()
+                    .statusCode(204);
+        }
+    }
 }
diff --git 
a/integration-tests-support/mock-backend/src/main/java/org/apache/camel/quarkus/test/mock/backend/MockBackendDisabled.java
 
b/integration-tests-support/mock-backend/src/main/java/org/apache/camel/quarkus/test/mock/backend/MockBackendDisabled.java
new file mode 100644
index 0000000..40d88fe
--- /dev/null
+++ 
b/integration-tests-support/mock-backend/src/main/java/org/apache/camel/quarkus/test/mock/backend/MockBackendDisabled.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.test.mock.backend;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * {@link BooleanSupplier} for use with 
org.apache.camel.quarkus.test.EnabledIf to enable tests if the mock back end is
+ * disabled.
+ */
+public class MockBackendDisabled implements BooleanSupplier {
+    @Override
+    public boolean getAsBoolean() {
+        return !MockBackendUtils.startMockBackend(false);
+    }
+}
diff --git a/integration-tests/azure-grouped/pom.xml 
b/integration-tests/azure-grouped/pom.xml
index 5f3b3c7..e69c42d 100644
--- a/integration-tests/azure-grouped/pom.xml
+++ b/integration-tests/azure-grouped/pom.xml
@@ -42,7 +42,7 @@
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-resteasy-jackson</artifactId>
+            <artifactId>quarkus-resteasy-jsonb</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
@@ -58,9 +58,17 @@
         </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-direct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-mock</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-seda</artifactId>
+        </dependency>
+        <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-junit5</artifactId>
             <scope>test</scope>
@@ -72,6 +80,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-test-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
             
<artifactId>camel-quarkus-integration-tests-support-azure</artifactId>
             <scope>test</scope>
         </dependency>
@@ -216,6 +229,19 @@
                 </dependency>
                 <dependency>
                     <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-direct-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
                     <artifactId>camel-quarkus-mock-deployment</artifactId>
                     <version>${project.version}</version>
                     <type>pom</type>
@@ -227,6 +253,19 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-seda-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
             </dependencies>
         </profile>
         <profile>

Reply via email to