You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2020/10/29 08:44:48 UTC

[camel-quarkus] branch master updated (38a59c6 -> 0f7b5e2)

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

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


    from 38a59c6  Updated CHANGELOG.md
     new 796fe87  Add WireMock test support
     new 0f7b5e2  Twilio native support

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../ROOT/pages/reference/extensions/twilio.adoc    |  14 +-
 .../ROOT/partials/reference/components/twilio.adoc |   6 +-
 extensions-jvm/pom.xml                             |   1 -
 .../twilio/deployment/TwilioProcessor.java         |  46 -----
 extensions-jvm/twilio/integration-test/pom.xml     |  82 --------
 .../component/twilio/it/TwilioResource.java        |  51 -----
 .../src/main/resources/application.properties      |  20 --
 .../quarkus/component/twilio/it/TwilioTest.java    |  34 ----
 extensions/pom.xml                                 |   1 +
 .../twilio/deployment/pom.xml                      |   8 +
 .../twilio/deployment/TwilioProcessor.java         |  56 ++++--
 {extensions-jvm => extensions}/twilio/pom.xml      |   1 -
 .../twilio/runtime/pom.xml                         |   9 +
 .../main/resources/META-INF/quarkus-extension.yaml |   3 +-
 integration-tests-support/pom.xml                  |   1 +
 integration-tests-support/wiremock/README.adoc     |  48 +++++
 .../{process-executor-support => wiremock}/pom.xml |  16 +-
 .../camel/quarkus/test/wiremock/MockServer.java    |  12 +-
 .../WireMockTestResourceLifecycleManager.java      | 211 +++++++++++++++++++++
 integration-tests/pom.xml                          |   1 +
 integration-tests/twilio/README.adoc               |  22 +++
 integration-tests/{vertx => twilio}/pom.xml        |  22 ++-
 .../component/twilio/it/TwilioResource.java        | 129 +++++++++++++
 .../quarkus/component/twilio/it/TwilioIT.java}     |   4 +-
 .../quarkus/component/twilio/it/TwilioTest.java}   |  56 +++---
 .../component/twilio/it/TwilioTestResource.java    |  51 +++++
 .../test/resources/mappings/twilioPhoneCall.json   |  31 +++
 .../resources/mappings/twilioPurchaseNumber.json   |  31 +++
 .../test/resources/mappings/twilioSendMessage.json |  31 +++
 pom.xml                                            |   1 +
 poms/bom-test/pom.xml                              |  28 +++
 tooling/scripts/test-categories.yaml               |   1 +
 32 files changed, 724 insertions(+), 304 deletions(-)
 delete mode 100644 extensions-jvm/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java
 delete mode 100644 extensions-jvm/twilio/integration-test/pom.xml
 delete mode 100644 extensions-jvm/twilio/integration-test/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
 delete mode 100644 extensions-jvm/twilio/integration-test/src/main/resources/application.properties
 delete mode 100644 extensions-jvm/twilio/integration-test/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java
 rename {extensions-jvm => extensions}/twilio/deployment/pom.xml (88%)
 copy extensions-support/google-http-client/deployment/src/main/java/org/apache/camel/quarkus/support/google/http/client/deployment/SupportGoogleHTTPClientProcessor.java => extensions/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java (50%)
 rename {extensions-jvm => extensions}/twilio/pom.xml (97%)
 rename {extensions-jvm => extensions}/twilio/runtime/pom.xml (91%)
 rename {extensions-jvm => extensions}/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml (97%)
 create mode 100644 integration-tests-support/wiremock/README.adoc
 copy integration-tests-support/{process-executor-support => wiremock}/pom.xml (77%)
 copy extensions/avro/runtime/src/main/java/org/apache/camel/quarkus/component/avro/BuildTimeAvroDataFormat.java => integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java (78%)
 create mode 100644 integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java
 create mode 100644 integration-tests/twilio/README.adoc
 copy integration-tests/{vertx => twilio}/pom.xml (87%)
 create mode 100644 integration-tests/twilio/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
 copy integration-tests/{amqp/src/test/java/org/apache/camel/quarkus/component/amqp/it/AmqpIT.java => twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioIT.java} (90%)
 copy integration-tests/{braintree/src/test/java/org/apache/camel/quarkus/component/braintree/it/BraintreeTest.java => twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java} (50%)
 create mode 100644 integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTestResource.java
 create mode 100644 integration-tests/twilio/src/test/resources/mappings/twilioPhoneCall.json
 create mode 100644 integration-tests/twilio/src/test/resources/mappings/twilioPurchaseNumber.json
 create mode 100644 integration-tests/twilio/src/test/resources/mappings/twilioSendMessage.json


[camel-quarkus] 02/02: Twilio native support

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0f7b5e22154c01fb6499f58cb67862a2f45f6799
Author: James Netherton <ja...@gmail.com>
AuthorDate: Wed Oct 28 09:04:57 2020 +0000

    Twilio native support
    
    Fixes #1633
---
 .../ROOT/pages/reference/extensions/twilio.adoc    |  14 ++-
 .../ROOT/partials/reference/components/twilio.adoc |   6 +-
 extensions-jvm/pom.xml                             |   1 -
 .../twilio/deployment/TwilioProcessor.java         |  46 --------
 .../component/twilio/it/TwilioResource.java        |  51 --------
 .../src/main/resources/application.properties      |  20 ----
 extensions/pom.xml                                 |   1 +
 .../twilio/deployment/pom.xml                      |   8 ++
 .../twilio/deployment/TwilioProcessor.java         |  95 +++++++++++++++
 {extensions-jvm => extensions}/twilio/pom.xml      |   1 -
 .../twilio/runtime/pom.xml                         |   9 ++
 .../main/resources/META-INF/quarkus-extension.yaml |   3 +-
 integration-tests/pom.xml                          |   1 +
 integration-tests/twilio/README.adoc               |  22 ++++
 .../twilio}/pom.xml                                |  79 ++++++++++---
 .../component/twilio/it/TwilioResource.java        | 129 +++++++++++++++++++++
 .../quarkus/component/twilio/it/TwilioIT.java      |  16 +--
 .../quarkus/component/twilio/it/TwilioTest.java    |  75 ++++++++++++
 .../component/twilio/it/TwilioTestResource.java    |  51 ++++++++
 .../test/resources/mappings/twilioPhoneCall.json   |  31 +++++
 .../resources/mappings/twilioPurchaseNumber.json   |  31 +++++
 .../test/resources/mappings/twilioSendMessage.json |  31 +++++
 tooling/scripts/test-categories.yaml               |   1 +
 23 files changed, 567 insertions(+), 155 deletions(-)

diff --git a/docs/modules/ROOT/pages/reference/extensions/twilio.adoc b/docs/modules/ROOT/pages/reference/extensions/twilio.adoc
index b6d990d..d0bfdd3 100644
--- a/docs/modules/ROOT/pages/reference/extensions/twilio.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/twilio.adoc
@@ -2,15 +2,15 @@
 // This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
 = Twilio
 :cq-artifact-id: camel-quarkus-twilio
-:cq-native-supported: false
-:cq-status: Preview
+:cq-native-supported: true
+:cq-status: Stable
 :cq-description: Interact with Twilio REST APIs using Twilio Java SDK.
 :cq-deprecated: false
 :cq-jvm-since: 1.1.0
-:cq-native-since: n/a
+:cq-native-since: 1.4.0
 
 [.badges]
-[.badge-key]##JVM since##[.badge-supported]##1.1.0## [.badge-key]##Native##[.badge-unsupported]##unsupported##
+[.badge-key]##JVM since##[.badge-supported]##1.1.0## [.badge-key]##Native since##[.badge-supported]##1.4.0##
 
 Interact with Twilio REST APIs using Twilio Java SDK.
 
@@ -31,3 +31,9 @@ Please refer to the above link for usage and configuration details.
 ----
 
 Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
+
+== SSL in native mode
+
+This extension auto-enables SSL support in native mode. Hence you do not need to add
+`quarkus.ssl.native=true` to your `application.properties` yourself. See also
+https://quarkus.io/guides/native-and-ssl[Quarkus SSL guide].
diff --git a/docs/modules/ROOT/partials/reference/components/twilio.adoc b/docs/modules/ROOT/partials/reference/components/twilio.adoc
index 6dc1b67..d666b93 100644
--- a/docs/modules/ROOT/partials/reference/components/twilio.adoc
+++ b/docs/modules/ROOT/partials/reference/components/twilio.adoc
@@ -2,11 +2,11 @@
 // This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
 :cq-artifact-id: camel-quarkus-twilio
 :cq-artifact-id-base: twilio
-:cq-native-supported: false
-:cq-status: Preview
+:cq-native-supported: true
+:cq-status: Stable
 :cq-deprecated: false
 :cq-jvm-since: 1.1.0
-:cq-native-since: n/a
+:cq-native-since: 1.4.0
 :cq-camel-part-name: twilio
 :cq-camel-part-title: Twilio
 :cq-camel-part-description: Interact with Twilio REST APIs using Twilio Java SDK.
diff --git a/extensions-jvm/pom.xml b/extensions-jvm/pom.xml
index 0d19d38..782c646 100644
--- a/extensions-jvm/pom.xml
+++ b/extensions-jvm/pom.xml
@@ -135,7 +135,6 @@
         <module>stub</module>
         <module>syslog</module>
         <module>thrift</module>
-        <module>twilio</module>
         <module>web3j</module>
         <module>weka</module>
         <module>wordpress</module>
diff --git a/extensions-jvm/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java b/extensions-jvm/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java
deleted file mode 100644
index a843176..0000000
--- a/extensions-jvm/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.twilio.deployment;
-
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.annotations.ExecutionTime;
-import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
-import io.quarkus.deployment.pkg.steps.NativeBuild;
-import org.apache.camel.quarkus.core.JvmOnlyRecorder;
-import org.jboss.logging.Logger;
-
-class TwilioProcessor {
-
-    private static final Logger LOG = Logger.getLogger(TwilioProcessor.class);
-    private static final String FEATURE = "camel-twilio";
-
-    @BuildStep
-    FeatureBuildItem feature() {
-        return new FeatureBuildItem(FEATURE);
-    }
-
-    /**
-     * Remove this once this extension starts supporting the native mode.
-     */
-    @BuildStep(onlyIf = NativeBuild.class)
-    @Record(value = ExecutionTime.RUNTIME_INIT)
-    void warnJvmInNative(JvmOnlyRecorder recorder) {
-        JvmOnlyRecorder.warnJvmInNative(LOG, FEATURE); // warn at build time
-        recorder.warnJvmInNative(FEATURE); // warn at runtime
-    }
-}
diff --git a/extensions-jvm/twilio/integration-test/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java b/extensions-jvm/twilio/integration-test/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
deleted file mode 100644
index 444e452..0000000
--- a/extensions-jvm/twilio/integration-test/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.twilio.it;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.apache.camel.CamelContext;
-import org.jboss.logging.Logger;
-
-@Path("/twilio")
-@ApplicationScoped
-public class TwilioResource {
-
-    private static final Logger LOG = Logger.getLogger(TwilioResource.class);
-
-    private static final String COMPONENT_TWILIO = "twilio";
-    @Inject
-    CamelContext context;
-
-    @Path("/load/component/twilio")
-    @GET
-    @Produces(MediaType.TEXT_PLAIN)
-    public Response loadComponentTwilio() throws Exception {
-        /* This is an autogenerated test */
-        if (context.getComponent(COMPONENT_TWILIO) != null) {
-            return Response.ok().build();
-        }
-        LOG.warnf("Could not load [%s] from the Camel context", COMPONENT_TWILIO);
-        return Response.status(500, COMPONENT_TWILIO + " could not be loaded from the Camel context").build();
-    }
-}
diff --git a/extensions-jvm/twilio/integration-test/src/main/resources/application.properties b/extensions-jvm/twilio/integration-test/src/main/resources/application.properties
deleted file mode 100644
index 16e6c31..0000000
--- a/extensions-jvm/twilio/integration-test/src/main/resources/application.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-## ---------------------------------------------------------------------------
-## 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.
-## ---------------------------------------------------------------------------
-
-# Bogus credentials are here to allow the component to get initialized. We won't connect to Twilio using these
-camel.component.twilio.username = bogus
-camel.component.twilio.password = bogus
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 20ebe54..49e7935 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -194,6 +194,7 @@
         <module>telegram</module>
         <module>tika</module>
         <module>timer</module>
+        <module>twilio</module>
         <module>twitter</module>
         <module>univocity-parsers</module>
         <module>validator</module>
diff --git a/extensions-jvm/twilio/deployment/pom.xml b/extensions/twilio/deployment/pom.xml
similarity index 88%
rename from extensions-jvm/twilio/deployment/pom.xml
rename to extensions/twilio/deployment/pom.xml
index caf7a61..207c8ba 100644
--- a/extensions-jvm/twilio/deployment/pom.xml
+++ b/extensions/twilio/deployment/pom.xml
@@ -31,6 +31,14 @@
 
     <dependencies>
         <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jackson-deployment</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-support-httpclient-deployment</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-core-deployment</artifactId>
         </dependency>
diff --git a/extensions/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java b/extensions/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java
new file mode 100644
index 0000000..51a5c2d
--- /dev/null
+++ b/extensions/twilio/deployment/src/main/java/org/apache/camel/quarkus/component/twilio/deployment/TwilioProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.twilio.deployment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import com.twilio.base.Creator;
+import com.twilio.base.Deleter;
+import com.twilio.base.Fetcher;
+import com.twilio.base.Reader;
+import com.twilio.base.Updater;
+import com.twilio.type.Endpoint;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
+import org.joda.time.DateTimeZone;
+
+class TwilioProcessor {
+
+    private static final String FEATURE = "camel-twilio";
+
+    @BuildStep
+    FeatureBuildItem feature() {
+        return new FeatureBuildItem(FEATURE);
+    }
+
+    @BuildStep
+    ExtensionSslNativeSupportBuildItem activateSslNativeSupport() {
+        return new ExtensionSslNativeSupportBuildItem(FEATURE);
+    }
+
+    @BuildStep
+    AdditionalApplicationArchiveMarkerBuildItem boxArchiveMarker() {
+        return new AdditionalApplicationArchiveMarkerBuildItem("com/twilio");
+    }
+
+    @BuildStep
+    void registerForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass, CombinedIndexBuildItem combinedIndex) {
+        IndexView index = combinedIndex.getIndex();
+
+        // Register Twilio API CRUD generator classes for reflection
+        String[] reflectiveClasses = Stream.of(Creator.class, Deleter.class, Fetcher.class, Reader.class, Updater.class)
+                .map(aClass -> aClass.getName())
+                .map(DotName::createSimple)
+                .flatMap(dotName -> index.getAllKnownSubclasses(dotName).stream())
+                .map(classInfo -> classInfo.name().toString())
+                .filter(className -> className.startsWith("com.twilio.rest.api.v2010"))
+                .toArray(String[]::new);
+        reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, reflectiveClasses));
+
+        // Register Twilio Endpoint implementors for reflection
+        String[] endpointImplementors = index.getAllKnownImplementors(DotName.createSimple(Endpoint.class.getName()))
+                .stream()
+                .map(classInfo -> classInfo.name().toString())
+                .toArray(String[]::new);
+
+        reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, endpointImplementors));
+    }
+
+    @BuildStep
+    NativeImageResourceBuildItem nativeImageResources() {
+        // Add Joda timezone resources into the native image as it is required by com.twilio.converter.DateConverter
+        List<String> timezones = new ArrayList<>();
+        for (String timezone : DateTimeZone.getAvailableIDs()) {
+            String[] zoneParts = timezone.split("/");
+            if (zoneParts.length == 2) {
+                timezones.add(String.format("org/joda/time/tz/data/%s/%s", zoneParts[0], zoneParts[1]));
+            }
+        }
+        return new NativeImageResourceBuildItem(timezones);
+    }
+}
diff --git a/extensions-jvm/twilio/pom.xml b/extensions/twilio/pom.xml
similarity index 97%
rename from extensions-jvm/twilio/pom.xml
rename to extensions/twilio/pom.xml
index 93d06e3..40d7480 100644
--- a/extensions-jvm/twilio/pom.xml
+++ b/extensions/twilio/pom.xml
@@ -33,6 +33,5 @@
     <modules>
         <module>deployment</module>
         <module>runtime</module>
-        <module>integration-test</module>
     </modules>
 </project>
diff --git a/extensions-jvm/twilio/runtime/pom.xml b/extensions/twilio/runtime/pom.xml
similarity index 91%
rename from extensions-jvm/twilio/runtime/pom.xml
rename to extensions/twilio/runtime/pom.xml
index e86d5d6..30aa967 100644
--- a/extensions-jvm/twilio/runtime/pom.xml
+++ b/extensions/twilio/runtime/pom.xml
@@ -32,6 +32,7 @@
 
     <properties>
         <camel.quarkus.jvmSince>1.1.0</camel.quarkus.jvmSince>
+        <camel.quarkus.nativeSince>1.4.0</camel.quarkus.nativeSince>
     </properties>
 
     <dependencyManagement>
@@ -48,6 +49,14 @@
 
     <dependencies>
         <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jackson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-support-httpclient</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-core</artifactId>
         </dependency>
diff --git a/extensions-jvm/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml
similarity index 97%
rename from extensions-jvm/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml
rename to extensions/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index bfeb129..ae48d6f 100644
--- a/extensions-jvm/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/twilio/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -24,9 +24,8 @@
 name: "Camel Twilio"
 description: "Interact with Twilio REST APIs using Twilio Java SDK"
 metadata:
-  unlisted: true
   guide: "https://camel.apache.org/camel-quarkus/latest/reference/extensions/twilio.html"
   categories:
   - "integration"
   status:
-  - "preview"
+  - "stable"
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 7ee0b1c..9a90095 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -161,6 +161,7 @@
         <module>tarfile</module>
         <module>telegram</module>
         <module>tika</module>
+        <module>twilio</module>
         <module>twitter</module>
         <module>univocity-parsers</module>
         <module>validator</module>
diff --git a/integration-tests/twilio/README.adoc b/integration-tests/twilio/README.adoc
new file mode 100644
index 0000000..b3b3c5f
--- /dev/null
+++ b/integration-tests/twilio/README.adoc
@@ -0,0 +1,22 @@
+== Camel Quarkus Twilio Integration Tests
+
+By default the Twilio integration tests use WireMock to stub the API interactions.
+
+To run the `camel-quarkus-twilio` integration tests against the real API, you must first create a Twilio account https://www.twilio.com/try-twilio.
+
+Then find your API https://www.twilio.com/docs/iam/test-credentials[test credentials] and set the following environment variables:
+
+[source,shell]
+----
+export TWILIO_USERNAME=your-user-name
+export TWILIO_PASSWORD=your-password
+export TWILIO_ACCOUNT_SID=your-account-sid
+----
+
+If the WireMock stub recordings need updating, then remove the existing files from `src/test/resources/mappings` and run tests with either:
+
+System property `-Dwiremock.record=true`
+
+Or
+
+Set environment variable `WIREMOCK_RECORD=true`
diff --git a/extensions-jvm/twilio/integration-test/pom.xml b/integration-tests/twilio/pom.xml
similarity index 50%
rename from extensions-jvm/twilio/integration-test/pom.xml
rename to integration-tests/twilio/pom.xml
index 450bef4..6d0819a 100644
--- a/extensions-jvm/twilio/integration-test/pom.xml
+++ b/integration-tests/twilio/pom.xml
@@ -21,25 +21,14 @@
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.camel.quarkus</groupId>
-        <artifactId>camel-quarkus-build-parent-it</artifactId>
+        <artifactId>camel-quarkus-integration-tests</artifactId>
         <version>1.4.0-SNAPSHOT</version>
-        <relativePath>../../../poms/build-parent-it/pom.xml</relativePath>
     </parent>
 
-    <artifactId>camel-quarkus-twilio-integration-test</artifactId>
-    <name>Camel Quarkus :: Twilio :: Integration Test</name>
+    <artifactId>camel-quarkus-integration-test-twilio</artifactId>
+    <name>Camel Quarkus :: Integration Tests :: Twilio</name>
     <description>Integration tests for Camel Quarkus Twilio extension</description>
 
-    <properties>
-        <!-- mvnd, a.k.a. Maven Daemon: https://github.com/mvndaemon/mvnd -->
-        <!-- The following rule tells mvnd to build the listed deployment modules before this module. -->
-        <!-- This is important because mvnd builds modules in parallel by default. The deployment modules are not -->
-        <!-- explicit dependencies of this module in the Maven sense, although they are required by the Quarkus Maven plugin. -->
-        <!-- Please update the rule whenever you change the dependencies of this module by running -->
-        <!--     mvn process-resources -Pformat    from the root directory -->
-        <mvnd.builder.rule>camel-quarkus-support-policy-deployment,camel-quarkus-twilio-deployment</mvnd.builder.rule>
-    </properties>
-
     <dependencyManagement>
         <dependencies>
             <dependency>
@@ -77,6 +66,68 @@
             <artifactId>rest-assured</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- The following dependencies guarantee that this module is built after them. You can update them by running `mvn process-resources -Pformat -N` from the source tree root directory -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-main-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-twilio-deployment</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>*</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <quarkus.package.type>native</quarkus.package.type>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git a/integration-tests/twilio/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java b/integration-tests/twilio/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
new file mode 100644
index 0000000..e53e28d
--- /dev/null
+++ b/integration-tests/twilio/src/main/java/org/apache/camel/quarkus/component/twilio/it/TwilioResource.java
@@ -0,0 +1,129 @@
+/*
+ * 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.twilio.it;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import com.twilio.http.HttpClient;
+import com.twilio.http.NetworkHttpClient;
+import com.twilio.http.Request;
+import com.twilio.http.TwilioRestClient;
+import com.twilio.rest.api.v2010.account.Call;
+import com.twilio.rest.api.v2010.account.IncomingPhoneNumber;
+import com.twilio.rest.api.v2010.account.Message;
+import io.quarkus.arc.Unremovable;
+import org.apache.camel.ProducerTemplate;
+
+@Path("/twilio")
+public class TwilioResource {
+
+    @Inject
+    ProducerTemplate producerTemplate;
+
+    @Path("/message")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    @Consumes(MediaType.TEXT_PLAIN)
+    public Response createMessage(String body) throws Exception {
+        Message message = producerTemplate.requestBody(
+                "twilio://message/create?from=RAW(+15005550006)&to=RAW(+14108675310)&body=" + body, null, Message.class);
+        return Response.ok(message.getSid()).build();
+    }
+
+    @Path("/purchase")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response purchasePhoneNumber() throws Exception {
+        IncomingPhoneNumber phoneNumber = producerTemplate.requestBody(
+                "twilio://incoming-phone-number/create?phonenumber=RAW(+15005550006)", null, IncomingPhoneNumber.class);
+        return Response.ok(phoneNumber.getPhoneNumber()).build();
+    }
+
+    @Path("/call")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response phoneCall() throws Exception {
+        Call call = producerTemplate.requestBody(
+                "twilio://call/create?from=RAW(+15005550006)&to=RAW(+14108675310)&url=http://demo.twilio.com/docs/voice.xml",
+                null, Call.class);
+        return Response.ok(call.getSid()).build();
+    }
+
+    @Unremovable
+    @Singleton
+    @Produces
+    @Named("restClient")
+    public TwilioRestClient restClient() {
+        // If mocking is enabled, we need to ensure Twilio API calls are directed to the mock server
+        String wireMockUrl = System.getProperty("wiremock.url");
+        if (wireMockUrl != null) {
+            HttpClient client = new NetworkHttpClient() {
+                @Override
+                public com.twilio.http.Response makeRequest(Request originalRequest) {
+                    String url = originalRequest.getUrl();
+
+                    Request modified = new Request(originalRequest.getMethod(),
+                            url.replace("https://api.twilio.com", wireMockUrl));
+
+                    Map<String, List<String>> headerParams = originalRequest.getHeaderParams();
+                    for (String key : headerParams.keySet()) {
+                        for (String value : headerParams.get(key)) {
+                            modified.addHeaderParam(key, value);
+                        }
+                    }
+
+                    Map<String, List<String>> postParams = originalRequest.getPostParams();
+                    for (String key : postParams.keySet()) {
+                        for (String value : postParams.get(key)) {
+                            modified.addPostParam(key, value);
+                        }
+                    }
+
+                    Map<String, List<String>> queryParams = originalRequest.getQueryParams();
+                    for (String key : queryParams.keySet()) {
+                        for (String value : queryParams.get(key)) {
+                            modified.addQueryParam(key, value);
+                        }
+                    }
+
+                    modified.setAuth(originalRequest.getUsername(), originalRequest.getPassword());
+
+                    return super.makeRequest(modified);
+                }
+            };
+
+            return new TwilioRestClient.Builder(
+                    System.getProperty("camel.component.twilio.username"),
+                    System.getProperty("camel.component.twilio.password"))
+                            .accountSid(System.getProperty("camel.component.twilio.account-sid"))
+                            .httpClient(client)
+                            .build();
+        }
+        return null;
+    }
+}
diff --git a/extensions-jvm/twilio/integration-test/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioIT.java
similarity index 70%
rename from extensions-jvm/twilio/integration-test/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java
rename to integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioIT.java
index 55b53b5..bc15dbf 100644
--- a/extensions-jvm/twilio/integration-test/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java
+++ b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioIT.java
@@ -16,19 +16,9 @@
  */
 package org.apache.camel.quarkus.component.twilio.it;
 
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
-import org.junit.jupiter.api.Test;
+import io.quarkus.test.junit.NativeImageTest;
 
-@QuarkusTest
-class TwilioTest {
-
-    @Test
-    public void loadComponentTwilio() {
-        /* A simple autogenerated test */
-        RestAssured.get("/twilio/load/component/twilio")
-                .then()
-                .statusCode(200);
-    }
+@NativeImageTest
+class TwilioIT extends TwilioTest {
 
 }
diff --git a/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java
new file mode 100644
index 0000000..e1b2a8a
--- /dev/null
+++ b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.twilio.it;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * Note: The scenarios tested here are the only ones supported with Twilio test credentials
+ *
+ * https://www.twilio.com/docs/iam/test-credentials
+ */
+@QuarkusTestResource(TwilioTestResource.class)
+@QuarkusTest
+class TwilioTest {
+
+    @Test
+    public void sendMessage() {
+        String messageId = RestAssured.given()
+                .body("Hello Camel Quarkus Twilio")
+                .post("/twilio/message")
+                .then()
+                .statusCode(200)
+                .extract()
+                .body()
+                .asString();
+
+        assertFalse(messageId.isEmpty());
+    }
+
+    @Test
+    public void purchasePhoneNumber() {
+        String phoneNumber = RestAssured.given()
+                .post("/twilio/purchase")
+                .then()
+                .statusCode(200)
+                .extract()
+                .body()
+                .asString();
+
+        assertEquals("+15005550006", phoneNumber);
+    }
+
+    @Test
+    public void phoneCall() {
+        String phoneNumber = RestAssured.given()
+                .post("/twilio/call")
+                .then()
+                .statusCode(200)
+                .extract()
+                .body()
+                .asString();
+
+        assertFalse(phoneNumber.isEmpty());
+    }
+}
diff --git a/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTestResource.java b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTestResource.java
new file mode 100644
index 0000000..1fca0cd
--- /dev/null
+++ b/integration-tests/twilio/src/test/java/org/apache/camel/quarkus/component/twilio/it/TwilioTestResource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.twilio.it;
+
+import java.util.Map;
+
+import org.apache.camel.quarkus.test.wiremock.WireMockTestResourceLifecycleManager;
+import org.apache.camel.util.CollectionHelper;
+
+public class TwilioTestResource extends WireMockTestResourceLifecycleManager {
+
+    private static final String TWILIO_API_BASE_URL = "https://api.twilio.com";
+    private static final String TWILIO_ENV_USERNAME = "TWILIO_USERNAME";
+    private static final String TWILIO_ENV_PASSWORD = "TWILIO_PASSWORD";
+    private static final String TWILIO_ENV_ACCOUNT_SID = "TWILIO_ACCOUNT_SID";
+
+    @Override
+    public Map<String, String> start() {
+        return CollectionHelper.mergeMaps(super.start(), CollectionHelper.mapOf(
+                "camel.component.twilio.username", envOrDefault(TWILIO_ENV_USERNAME, "test"),
+                "camel.component.twilio.password", envOrDefault(TWILIO_ENV_PASSWORD, "2se3r3t"),
+                "camel.component.twilio.account-sid", envOrDefault(TWILIO_ENV_ACCOUNT_SID, "test")));
+    }
+
+    @Override
+    public String getRecordTargetBaseUrl() {
+        return TWILIO_API_BASE_URL;
+    }
+
+    @Override
+    public boolean isMockingEnabled() {
+        return !envVarsPresent(
+                TWILIO_ENV_USERNAME,
+                TWILIO_ENV_PASSWORD,
+                TWILIO_ENV_ACCOUNT_SID);
+    }
+}
diff --git a/integration-tests/twilio/src/test/resources/mappings/twilioPhoneCall.json b/integration-tests/twilio/src/test/resources/mappings/twilioPhoneCall.json
new file mode 100644
index 0000000..301e366
--- /dev/null
+++ b/integration-tests/twilio/src/test/resources/mappings/twilioPhoneCall.json
@@ -0,0 +1,31 @@
+{
+  "id" : "5857027d-5598-4769-b79e-78951d1a8b8d",
+  "name" : "2010-04-01_accounts_test_callsjson",
+  "request" : {
+    "url" : "/2010-04-01/Accounts/test/Calls.json",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalTo" : "To=%2B14108675310&From=%2B15005550006&Url=http%3A%2F%2Fdemo.twilio.com%2Fdocs%2Fvoice.xml",
+      "caseInsensitive" : false
+    } ]
+  },
+  "response" : {
+    "status" : 201,
+    "body" : "{\"date_updated\": \"Tue, 27 Oct 2020 14:14:02 +0000\", \"price_unit\": \"USD\", \"parent_call_sid\": null, \"caller_name\": null, \"duration\": null, \"from\": \"+15005550006\", \"to\": \"+14108675310\", \"annotation\": null, \"answered_by\": null, \"sid\": \"CA66b4ff97854621f04fea0e49f92f66ea\", \"queue_time\": \"0\", \"price\": null, \"api_version\": \"2010-04-01\", \"status\": \"queued\", \"direction\": null, \"start_time\": null, \"date_created\": \"Tue, 27 Oct 2020 14 [...]
+    "headers" : {
+      "Date" : "Tue, 27 Oct 2020 14:14:02 GMT",
+      "Content-Type" : "application/json",
+      "Twilio-Concurrent-Requests" : "1",
+      "Twilio-Request-Id" : "RQ3e5e14c2989a443ab231b79611748321",
+      "Twilio-Request-Duration" : "0.018",
+      "X-Powered-By" : "AT-5000",
+      "X-Shenanigans" : "none",
+      "X-Home-Region" : "us1",
+      "X-API-Domain" : "api.twilio.com",
+      "Strict-Transport-Security" : "max-age=31536000"
+    }
+  },
+  "uuid" : "5857027d-5598-4769-b79e-78951d1a8b8d",
+  "persistent" : true,
+  "insertionIndex" : 1
+}
\ No newline at end of file
diff --git a/integration-tests/twilio/src/test/resources/mappings/twilioPurchaseNumber.json b/integration-tests/twilio/src/test/resources/mappings/twilioPurchaseNumber.json
new file mode 100644
index 0000000..65d5698
--- /dev/null
+++ b/integration-tests/twilio/src/test/resources/mappings/twilioPurchaseNumber.json
@@ -0,0 +1,31 @@
+{
+  "id" : "49f6b8ea-9fc0-4804-bda4-46c0f5304bb0",
+  "name" : "2010-04-01_accounts_test_incomingphonenumbersjson",
+  "request" : {
+    "url" : "/2010-04-01/Accounts/test/IncomingPhoneNumbers.json",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalTo" : "PhoneNumber=%2B15005550006",
+      "caseInsensitive" : false
+    } ]
+  },
+  "response" : {
+    "status" : 201,
+    "body" : "{\"sid\": \"PNd0a95f9f5b0dabf14b472bfd4b4b72d1\", \"account_sid\": \"test\", \"friendly_name\": \"(500) 555-0006\", \"phone_number\": \"+15005550006\", \"voice_url\": null, \"voice_method\": \"POST\", \"voice_fallback_url\": null, \"voice_fallback_method\": \"POST\", \"voice_caller_id_lookup\": false, \"date_created\": \"Tue, 27 Oct 2020 14:14:03 +0000\", \"date_updated\": \"Tue, 27 Oct 2020 14:14:03 +0000\", \"sms_url\": \"\", \"sms_method\": \"POST\", \"sms_fallback_url\" [...]
+    "headers" : {
+      "Date" : "Tue, 27 Oct 2020 14:14:03 GMT",
+      "Content-Type" : "application/json",
+      "Twilio-Concurrent-Requests" : "1",
+      "Twilio-Request-Id" : "RQ3b188151ab1440a89a2b754c23500c44",
+      "Twilio-Request-Duration" : "0.226",
+      "X-Powered-By" : "AT-5000",
+      "X-Shenanigans" : "none",
+      "X-Home-Region" : "us1",
+      "X-API-Domain" : "api.twilio.com",
+      "Strict-Transport-Security" : "max-age=31536000"
+    }
+  },
+  "uuid" : "49f6b8ea-9fc0-4804-bda4-46c0f5304bb0",
+  "persistent" : true,
+  "insertionIndex" : 3
+}
\ No newline at end of file
diff --git a/integration-tests/twilio/src/test/resources/mappings/twilioSendMessage.json b/integration-tests/twilio/src/test/resources/mappings/twilioSendMessage.json
new file mode 100644
index 0000000..88cedb8
--- /dev/null
+++ b/integration-tests/twilio/src/test/resources/mappings/twilioSendMessage.json
@@ -0,0 +1,31 @@
+{
+  "id" : "86fb6910-1118-4067-b2e1-9520828b76a9",
+  "name" : "2010-04-01_accounts_test_messagesjson",
+  "request" : {
+    "url" : "/2010-04-01/Accounts/test/Messages.json",
+    "method" : "POST",
+    "bodyPatterns" : [ {
+      "equalTo" : "To=%2B14108675310&From=%2B15005550006&Body=Hello+Camel+Quarkus+Twilio",
+      "caseInsensitive" : false
+    } ]
+  },
+  "response" : {
+    "status" : 201,
+    "body" : "{\"sid\": \"SM2691e2c49c654cf1bbc96300f3d68b2b\", \"date_created\": \"Tue, 27 Oct 2020 14:14:03 +0000\", \"date_updated\": \"Tue, 27 Oct 2020 14:14:03 +0000\", \"date_sent\": null, \"account_sid\": \"test\", \"to\": \"+14108675310\", \"from\": \"+15005550006\", \"messaging_service_sid\": null, \"body\": \"Hello Camel Quarkus Twilio\", \"status\": \"queued\", \"num_segments\": \"1\", \"num_media\": \"0\", \"direction\": \"outbound-api\", \"api_version\": \"2010-04-01\", \"pr [...]
+    "headers" : {
+      "Date" : "Tue, 27 Oct 2020 14:14:03 GMT",
+      "Content-Type" : "application/json",
+      "Twilio-Concurrent-Requests" : "1",
+      "Twilio-Request-Id" : "RQd1b0d893e7c148d1b3a1ded5c2f0fa76",
+      "Twilio-Request-Duration" : "0.042",
+      "X-Powered-By" : "AT-5000",
+      "X-Shenanigans" : "none",
+      "X-Home-Region" : "us1",
+      "X-API-Domain" : "api.twilio.com",
+      "Strict-Transport-Security" : "max-age=31536000"
+    }
+  },
+  "uuid" : "86fb6910-1118-4067-b2e1-9520828b76a9",
+  "persistent" : true,
+  "insertionIndex" : 2
+}
\ No newline at end of file
diff --git a/tooling/scripts/test-categories.yaml b/tooling/scripts/test-categories.yaml
index 70b6133..7d9695a 100644
--- a/tooling/scripts/test-categories.yaml
+++ b/tooling/scripts/test-categories.yaml
@@ -148,3 +148,4 @@ saas:
   - sap-netweaver
   - servicenow
   - slack
+  - twilio


[camel-quarkus] 01/02: Add WireMock test support

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 796fe87c1d84880c57651c50176a40ca79191a5d
Author: James Netherton <ja...@gmail.com>
AuthorDate: Wed Oct 28 08:52:58 2020 +0000

    Add WireMock test support
---
 integration-tests-support/pom.xml                  |   1 +
 integration-tests-support/wiremock/README.adoc     |  48 +++++
 integration-tests-support/wiremock/pom.xml         |  62 ++++++
 .../camel/quarkus/test/wiremock/MockServer.java    |  32 ++++
 .../WireMockTestResourceLifecycleManager.java      | 211 +++++++++++++++++++++
 pom.xml                                            |   1 +
 poms/bom-test/pom.xml                              |  28 +++
 7 files changed, 383 insertions(+)

diff --git a/integration-tests-support/pom.xml b/integration-tests-support/pom.xml
index cd67962..8c8ad16 100644
--- a/integration-tests-support/pom.xml
+++ b/integration-tests-support/pom.xml
@@ -45,6 +45,7 @@
         <module>test-support</module>
         <module>testcontainers-support</module>
         <module>mock-backend</module>
+        <module>wiremock</module>
     </modules>
 
 </project>
diff --git a/integration-tests-support/wiremock/README.adoc b/integration-tests-support/wiremock/README.adoc
new file mode 100644
index 0000000..0b477e7
--- /dev/null
+++ b/integration-tests-support/wiremock/README.adoc
@@ -0,0 +1,48 @@
+== WireMock test support
+
+This module provides test support for http://wiremock.org/[WireMock]. This enables the HTTP interactions between Camel & third party services to be
+stubbed, recorded & replayed.
+
+=== Usage
+
+Add the following test scoped dependency into the extension integration test pom.xml:
+
+[source,xml]
+----
+<dependency>
+    <groupId>org.apache.camel.quarkus</groupId>
+    <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+    <scope>test</scope>
+</dependency>
+----
+
+Next create a class that extends the abstract `WireMockTestResourceLifecycleManager`. You'll need to implement abstract methods:
+
+* `getRecordTargetBaseUrl` - To specify the base URL of the service interactions to be recorded
+* `isMockingEnabled` - To determine whether the test should start the mock server or invoke the real service
+
+You can also override the `start` method to perform custom initialization logic and return additional configuration properties that Camel components may need.
+
+`WireMockTestResourceLifecycleManager` sets a system property named `wiremock.url`, which is the base URL to the running WireMock server. 
+In playback mode, you'll need to configure the Camel component under test to direct its API calls to this URL.
+
+==== Recording HTTP interactions
+
+The fundamentals of WireMock record and playback are documented http://wiremock.org/docs/record-playback/[here]. Setup of the `WireMockServer` is already handled by
+`WireMockTestResourceLifecycleManager`. All you need to do is ensure directory `src/test/resources/mappings` exists and to trigger recording by either:
+
+System property `-Dwiremock.record=true`
+
+Or
+
+Environment variable `WIREMOCK_RECORD=true`
+
+When all tests complete, the recorded HTTP interactions will show up in the 'mappings' directory. The recorded stub file names are quite complex, feel free
+to update them to something more human friendly.
+
+By default, stub mapping files are not saved when requests return an unsuccessful response code. You can alter this behaviour by overriding method `isDeleteRecordedMappingsOnError`.
+
+It's important to inspect the recorded files for the presence of any real API keys, secrets or passwords and replace them with made up values.
+
+WireMock generates new stub files on each recording, so it's a good idea to remove the existing contents from the 'mappings' directory
+before a recording run.
diff --git a/integration-tests-support/wiremock/pom.xml b/integration-tests-support/wiremock/pom.xml
new file mode 100644
index 0000000..320ee44
--- /dev/null
+++ b/integration-tests-support/wiremock/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.camel.quarkus</groupId>
+        <artifactId>camel-quarkus-integration-tests-support</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+    <name>Camel Quarkus :: Integration Tests :: WireMock :: Support</name>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.camel.quarkus</groupId>
+                <artifactId>camel-quarkus-bom-test</artifactId>
+                <version>${project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-test-support</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-integration-test-support-mock-backend</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java
new file mode 100644
index 0000000..b6f29f1
--- /dev/null
+++ b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/MockServer.java
@@ -0,0 +1,32 @@
+/*
+ * 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.wiremock;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used by the companion WireMockTestResourceLifecycleManager to inject
+ * {@link com.github.tomakehurst.wiremock.WireMockServer} instances into
+ * test classes.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface MockServer {
+}
diff --git a/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java
new file mode 100644
index 0000000..dc64c87
--- /dev/null
+++ b/integration-tests-support/wiremock/src/main/java/org/apache/camel/quarkus/test/wiremock/WireMockTestResourceLifecycleManager.java
@@ -0,0 +1,211 @@
+/*
+ * 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.wiremock;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.recording.RecordingStatus;
+import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.quarkus.test.mock.backend.MockBackendUtils;
+import org.jboss.logging.Logger;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.recordSpec;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+
+public abstract class WireMockTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
+
+    protected static final Logger LOG = Logger.getLogger(WireMockTestResourceLifecycleManager.class);
+    protected WireMockServer server;
+
+    /**
+     * Starts the {@link WireMockServer} and configures request / response recording if it has been enabled
+     */
+    @Override
+    public Map<String, String> start() {
+        Map<String, String> properties = new HashMap<>();
+
+        if (isMockingEnabled() || isRecordingEnabled()) {
+            server = createServer();
+            server.start();
+
+            if (isRecordingEnabled()) {
+                String recordTargetBaseUrl = getRecordTargetBaseUrl();
+                if (recordTargetBaseUrl != null) {
+                    LOG.infof("Enabling WireMock recording for %s", recordTargetBaseUrl);
+                    server.startRecording(recordSpec()
+                            .forTarget(recordTargetBaseUrl)
+                            .allowNonProxied(false));
+                } else {
+                    throw new IllegalStateException(
+                            "Must return a non-null value from getRecordTargetBaseUrl() in order to support WireMock recording");
+                }
+            }
+
+            String wireMockUrl = "http://localhost:" + server.port();
+            LOG.infof("WireMock started on %s", wireMockUrl);
+            properties.put("wiremock.url", wireMockUrl);
+        }
+
+        return properties;
+    }
+
+    /**
+     * Stops the {@link WireMockServer} instance if it was started and stops recording if record mode was enabled
+     */
+    @Override
+    public void stop() {
+        if (server != null) {
+            LOG.info("Stopping WireMockServer");
+            if (server.getRecordingStatus().getStatus().equals(RecordingStatus.Recording)) {
+                LOG.info("Stopping recording");
+                SnapshotRecordResult recordResult = server.stopRecording();
+
+                List<StubMapping> stubMappings = recordResult.getStubMappings();
+                if (isDeleteRecordedMappingsOnError()) {
+                    for (StubMapping mapping : stubMappings) {
+                        int status = mapping.getResponse().getStatus();
+                        if (status >= 300 && mapping.shouldBePersisted()) {
+                            try {
+                                String fileName = mapping.getName() + "-" + mapping.getId() + ".json";
+                                Path mappingFilePath = Paths.get("./src/test/resources/mappings/", fileName);
+                                Files.deleteIfExists(mappingFilePath);
+                                LOG.infof("Deleted mapping file %s as status code was %d", fileName, status);
+                            } catch (IOException e) {
+                                LOG.errorf("Failed to delete mapping file %s", e, mapping.getName());
+                            }
+                        }
+                    }
+                }
+            }
+            server.stop();
+        }
+    }
+
+    /**
+     * If mocking is enabled, inject an instance of {@link WireMockServer} into any fields
+     * annotated with {@link MockServer}. This gives full control over creating recording rules
+     * and some aspects of the server lifecycle.
+     *
+     * The server instance is not injected if mocking is explicitly disabled, and therefore the resulting
+     * {@link MockServer} annotated field value will be null.
+     */
+    @Override
+    public void inject(Object testInstance) {
+        if (isMockingEnabled() || isRecordingEnabled()) {
+            Class<?> c = testInstance.getClass();
+            for (Field field : c.getDeclaredFields()) {
+                if (field.getAnnotation(MockServer.class) != null) {
+                    if (!WireMockServer.class.isAssignableFrom(field.getType())) {
+                        throw new RuntimeException("@MockServer can only be used on fields of type WireMockServer");
+                    }
+
+                    field.setAccessible(true);
+                    try {
+                        if (server == null) {
+                            server = createServer();
+                            server.start();
+                        }
+                        LOG.infof("Injecting WireMockServer for field %s", field.getName());
+                        field.set(testInstance, server);
+                        return;
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Determines whether each of the given environment variable names is set
+     */
+    protected boolean envVarsPresent(String... envVarNames) {
+        if (envVarNames.length == 0) {
+            throw new IllegalArgumentException("envVarNames must not be empty");
+        }
+
+        boolean present = true;
+        for (String envVar : envVarNames) {
+            if (System.getenv(envVar) == null) {
+                present = false;
+                break;
+            }
+        }
+
+        return present;
+    }
+
+    /**
+     * Get the value of a given environment variable or a default value if it does not exist
+     */
+    protected String envOrDefault(String envVarName, String defaultValue) {
+        String value = System.getenv(envVarName);
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * Whether recorded stub mapping files should be deleted if the HTTP response was an error code (>= 400).
+     *
+     * By default this returns true. Can be overridden if an error response is desired / expected from the HTTP request.
+     */
+    protected boolean isDeleteRecordedMappingsOnError() {
+        return true;
+    }
+
+    /**
+     * The target base URL that WireMock should watch for when recording requests.
+     *
+     * For example, if a test triggers an HTTP call on an external endpoint like https://api.foo.com/some/resource.
+     * Then the base URL would be https://api.foo.com
+     */
+    protected abstract String getRecordTargetBaseUrl();
+
+    /**
+     * Conditions under which the {@link WireMockServer} should be started.
+     */
+    protected abstract boolean isMockingEnabled();
+
+    /**
+     * Creates and starts a {@link WireMockServer} on a random port. {@link MockBackendUtils} triggers the log
+     * message that signifies mocking is in use.
+     */
+    private WireMockServer createServer() {
+        LOG.info("Starting WireMockServer");
+        MockBackendUtils.startMockBackend(true);
+        return new WireMockServer(options().dynamicPort());
+    }
+
+    /**
+     * Determine whether to enable WireMock record mode:
+     *
+     * http://wiremock.org/docs/record-playback/
+     */
+    private boolean isRecordingEnabled() {
+        String recordEnabled = System.getProperty("wiremock.record", System.getenv("WIREMOCK_RECORD"));
+        return recordEnabled != null && recordEnabled.equals("true");
+    }
+}
diff --git a/pom.xml b/pom.xml
index 892e91d..5274b94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,7 @@
         <sshd.version>2.3.0</sshd.version>
         <stax2.version>4.2</stax2.version>
         <testcontainers.version>1.14.3</testcontainers.version>
+        <wiremock.version>2.27.2</wiremock.version>
         <zt-exec.version>1.11</zt-exec.version>
 
         <!-- Maven plugin versions (keep sorted alphabetically) -->
diff --git a/poms/bom-test/pom.xml b/poms/bom-test/pom.xml
index 1d970b6..1ffb844 100644
--- a/poms/bom-test/pom.xml
+++ b/poms/bom-test/pom.xml
@@ -98,6 +98,11 @@
                 <artifactId>camel-quarkus-integration-testcontainers-support</artifactId>
                 <version>${camel-quarkus.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.camel.quarkus</groupId>
+                <artifactId>camel-quarkus-integration-wiremock-support</artifactId>
+                <version>${camel-quarkus.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.ftpserver</groupId>
@@ -166,6 +171,29 @@
                 </exclusions>
             </dependency>
             <dependency>
+                <groupId>com.github.tomakehurst</groupId>
+                <artifactId>wiremock-jre8</artifactId>
+                <version>${wiremock.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>javax.xml.bind</groupId>
+                        <artifactId>jaxb-api</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.checkerframework</groupId>
+                        <artifactId>checker-qual</artifactId>
+                    </exclusion>
+                    <exclusion> <!-- fix dependencyConvergence clash with junit-jupiter -->
+                        <groupId>org.opentest4j</groupId>
+                        <artifactId>opentest4j</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>com.google.code.findbugs</groupId>
+                        <artifactId>jsr305</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
                 <groupId>org.zeroturnaround</groupId>
                 <artifactId>zt-exec</artifactId>
                 <version>${zt-exec.version}</version>