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>