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 5cb73cad45 Fixes #7373 extend saga coverage
5cb73cad45 is described below

commit 5cb73cad45972b1b1cd0db6bb0004ee973434039
Author: Jiri Ondrusek <ondrusek.j...@gmail.com>
AuthorDate: Fri May 16 16:12:37 2025 +0200

    Fixes #7373 extend saga coverage
---
 integration-tests/saga/pom.xml                     |  96 +++++++++++++++
 .../quarkus/component/saga/it/SagaResource.java    |  99 +++++++++++++++
 .../camel/quarkus/component/saga/it/SagaRoute.java | 133 +++++++++++++++++++++
 .../component/saga/it/lra/LraCreditService.java    |  61 ++++++++++
 .../quarkus/component/saga/it/lra/LraService.java  |  58 +++++++++
 .../component/saga/it/lra/LraTicketService.java    |  70 +++++++++++
 .../saga/it/lra/LraTicketServiceStatus.java}       |  29 +----
 .../saga/src/main/resources/application.properties |  19 +++
 .../saga/src/main/resources/routes/saga-routes.xml |  54 +++++++++
 .../camel/quarkus/component/saga/it/SagaTest.java  | 132 ++++++++++++++++++++
 10 files changed, 728 insertions(+), 23 deletions(-)

diff --git a/integration-tests/saga/pom.xml b/integration-tests/saga/pom.xml
index fe4c9c06c2..5753aa6511 100644
--- a/integration-tests/saga/pom.xml
+++ b/integration-tests/saga/pom.xml
@@ -39,6 +39,14 @@
             <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>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-lra</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-bean</artifactId>
@@ -47,6 +55,26 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-resteasy</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-jackson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-xml-io-dsl</artifactId>
+        </dependency>
+
+        <!-- Messaging extension to test -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-jms</artifactId>
+        </dependency>
+
+        <!-- The JMS client library to test with -->
+        <dependency>
+            <groupId>io.quarkiverse.artemis</groupId>
+            <artifactId>quarkus-artemis-jms</artifactId>
+        </dependency>
 
         <!-- test dependencies -->
         <dependency>
@@ -59,6 +87,11 @@
             <artifactId>rest-assured</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
@@ -124,6 +157,45 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-jms-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>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-lra-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-saga-deployment</artifactId>
@@ -137,8 +209,32 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    
<artifactId>camel-quarkus-xml-io-dsl-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
             </dependencies>
         </profile>
+        <profile>
+            <id>skip-testcontainers-tests</id>
+            <activation>
+                <property>
+                    <name>skip-testcontainers-tests</name>
+                </property>
+            </activation>
+            <properties>
+                <skipTests>true</skipTests>
+            </properties>
+        </profile>
     </profiles>
 
 </project>
diff --git 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaResource.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaResource.java
index e2ad657750..f7cc8c8763 100644
--- 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaResource.java
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaResource.java
@@ -16,14 +16,20 @@
  */
 package org.apache.camel.quarkus.component.saga.it;
 
+import java.util.Map;
+
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import org.apache.camel.CamelContext;
+import org.apache.camel.quarkus.component.saga.it.lra.LraCreditService;
+import org.apache.camel.quarkus.component.saga.it.lra.LraService;
+import org.apache.camel.quarkus.component.saga.it.lra.LraTicketService;
 import org.jboss.logging.Logger;
 
 @Path("/saga")
@@ -43,6 +49,15 @@ public class SagaResource {
     @Inject
     CreditService creditService;
 
+    @Inject
+    LraCreditService lraCreditService;
+
+    @Inject
+    LraTicketService lraTicketService;
+
+    @Inject
+    LraService lraService;
+
     @Path("/load/component/saga")
     @GET
     @Produces(MediaType.TEXT_PLAIN)
@@ -94,4 +109,88 @@ public class SagaResource {
             }
         }
     }
+
+    @Path("/lraSaga/{id}/{credit}/{trainCost}/{flightCost}")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response lraSaga(@PathParam("id") int id,
+            @PathParam("credit") int credit,
+            @PathParam("trainCost") int trainCost,
+            @PathParam("flightCost") int flightCost) throws 
InterruptedException {
+
+        //initialize total credit
+        lraCreditService.setTotalCredit(credit);
+        lraTicketService.reset();
+
+        try {
+            context.createFluentProducerTemplate().to("direct:lraSaga")
+                    .withHeader("id", id)
+                    .withHeader("trainCost", trainCost)
+                    .withHeader("flightCost", flightCost)
+                    .request();
+        } catch (Exception e) {
+            return getResponse(500);
+        }
+
+        return getResponse(200);
+    }
+
+    private Response getResponse(int status) throws InterruptedException {
+        // wait for the orders being cancelled
+        Thread.sleep(500);
+        Map result = Map.of("creditBalance", lraCreditService.getCredit(), 
"train",
+                lraTicketService.getTrain(),
+                "flight", lraTicketService.getFlight());
+
+        return Response.status(status).entity(result).build();
+    }
+
+    @Path("/timeout/{timeout}")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response timeout(@PathParam("timeout") int timeout) throws 
InterruptedException {
+        Object o = 
context.createFluentProducerTemplate().to("direct:newOrderTimeout5sec")
+                .withHeader("timeout", timeout)
+                .request();
+        return Response.ok().entity(o).build();
+    }
+
+    @Path("/manualSaga/{complete}")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response manualSaga(@PathParam("complete") boolean complete) throws 
InterruptedException {
+
+        lraService.setCompleted(false);
+
+        context.createFluentProducerTemplate().to("direct:manualSaga")
+                .withHeader("shouldComplete", complete)
+                .request();
+
+        return Response.ok().build();
+    }
+
+    @Path("/manualCompleted")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response manualCompensated() throws InterruptedException {
+
+        return Response.ok().entity(lraService.isCompleted()).build();
+    }
+
+    @Path("/xmlSaga/{complete}")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response xmlSaga(@PathParam("complete") boolean complete) throws 
InterruptedException {
+        lraService.setXmlCompleted(false);
+        try {
+            context.createFluentProducerTemplate().to("direct:xmlSaga")
+                    .withHeader("complete", complete)
+                    .request();
+        } catch (Exception e) {
+            return 
Response.status(500).entity(e.getCause().getMessage()).build();
+        }
+        //time to complete
+        Thread.sleep(500);
+        return Response.ok().entity(lraService.isXmlCompleted()).build();
+    }
 }
diff --git 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaRoute.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaRoute.java
index d6dcea6124..8fe8498c3a 100644
--- 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaRoute.java
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/SagaRoute.java
@@ -16,11 +16,17 @@
  */
 package org.apache.camel.quarkus.component.saga.it;
 
+import java.util.concurrent.TimeUnit;
+
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.SagaCompletionMode;
 import org.apache.camel.model.SagaPropagation;
+import org.apache.camel.quarkus.component.saga.it.lra.LraCreditService;
+import org.apache.camel.quarkus.component.saga.it.lra.LraService;
+import org.apache.camel.quarkus.component.saga.it.lra.LraTicketService;
 import org.apache.camel.saga.CamelSagaService;
 import org.apache.camel.saga.InMemorySagaService;
 
@@ -32,6 +38,15 @@ public class SagaRoute extends RouteBuilder {
     @Inject
     CreditService creditService;
 
+    @Inject
+    LraService lraService;
+
+    @Inject
+    LraCreditService lraCreditService;
+
+    @Inject
+    LraTicketService lraTicketService;
+
     @Override
     public void configure() throws Exception {
         CamelSagaService sagaService = new InMemorySagaService();
@@ -63,5 +78,123 @@ public class SagaRoute extends RouteBuilder {
         
from("direct:finalize").saga().propagation(SagaPropagation.MANDATORY).choice()
                 
.when(header("fail").isEqualTo(true)).to("saga:COMPENSATE").end();
 
+        // ---------------------- saga with JMS using custom ids / timeouts / 
completion / Long-Running-Action header
+
+        from("direct:lraSaga")
+                .saga()
+                .compensation("direct:lraCancelOrder")
+                .completion("direct:lraCompleted")
+                .log("Executing saga #${header.id} with LRA 
${header.Long-Running-Action}")
+                .setHeader("payFor", constant("train"))
+                .setHeader("amount", header("trainCost"))
+                .to("jms:queue:train?exchangePattern=InOut" +
+                        "&replyTo=train.reply")
+                .log("train seat reserved for saga #${header.id} with payment 
transaction: ${body}")
+                .setHeader("payFor", constant("flight"))
+                .setHeader("amount", header("flightCost"))
+                .to("jms:queue:flight?exchangePattern=InOut" +
+                        "&replyTo=flight.reply")
+                .log("flight booked for saga #${header.id} with payment 
transaction: ${body}")
+                .setBody(header("Long-Running-Action"))
+                .end();
+
+        from("direct:lraCancelOrder")
+                .log("Transaction ${header.Long-Running-Action} has been 
cancelled due to flight or train insufficient payment, refunding all tickets")
+                .bean(lraCreditService, "refundCredit")
+                .bean(lraTicketService, "setTicketsRefunded")
+                .log("Credit for action ${body} refunded");
+
+        from("direct:lraCompleted")
+                .log("Transaction ${header.Long-Running-Action} has been 
completed.")
+                .bean(lraTicketService, "setTicketsReserved");
+
+        //train
+        from("jms:queue:train")
+                .saga()
+                .propagation(SagaPropagation.MANDATORY)
+                .option("id", header("id"))
+                .compensation("direct:lraTrainCancelPurchase")
+                .log("Buying train #${header.id}")
+                .to("jms:queue:payment?exchangePattern=InOut" +
+                        "&replyTo=payment.train.reply")
+                .log("Payment for train #${header.id} done with transaction 
${body}")
+                .end();
+
+        from("direct:lraTrainCancelPurchase")
+                .log("Train purchase #${header.id} has been cancelled due to 
payment failure");
+
+        //flight
+        from("jms:queue:flight")
+                .saga()
+                .propagation(SagaPropagation.MANDATORY)
+                .option("id", header("id"))
+                .compensation("direct:lraFlightCancelPurchase")
+                .log("Buying flight #${header.id}")
+                .to("jms:queue:payment?exchangePattern=InOut" +
+                        "&replyTo={payment.flight.reply")
+                .log("Payment for flight #${header.id} done with transaction 
${body}")
+                .end();
+
+        from("direct:lraFlightCancelPurchase")
+                .log("Flight purchase #${header.id} has been cancelled due to 
payment failure");
+
+        from("jms:queue:payment")
+                .routeId("payment-service")
+                .saga()
+                .propagation(SagaPropagation.MANDATORY)
+                .option("id", header("id"))
+                .option("payFor", header("payFor"))
+                .compensation("direct:lraCancelPayment")
+                .log("Paying ${header.payFor} (${header.amount}) for order 
#${header.id}")
+                .bean(lraCreditService, "reserveCredit")
+                .setBody(header("JMSCorrelationID"))
+                .log("Payment ${header.payFor} done for order #${header.id} 
with payment transaction ${body}")
+                .end();
+
+        from("direct:lraCancelPayment")
+                .routeId("payment-cancel")
+                .choice()
+                
.when(header("payFor").contains("train")).bean(lraTicketService, 
"setTrainError")
+                
.when(header("payFor").contains("flight")).bean(lraTicketService, 
"setFlightError")
+                .endChoice()
+                .log("Payment for order #${header.id} did not finish 
(insufficient credit)");
+
+        // ----------------- timeout ------------------------------
+
+        from("direct:newOrderTimeout5sec")
+                .saga()
+                .timeout(5, TimeUnit.SECONDS) // newOrder requires that the 
saga is completed within 5 seconds
+                .propagation(SagaPropagation.REQUIRES_NEW)
+                .compensation("direct:cancelOrderTimeout5sec")
+                .bean(lraService, "sleep")
+                .setBody(constant("success"))
+                .log("Order ${body} created");
+
+        from("direct:cancelOrderTimeout5sec")
+                .saga()
+                .propagation(SagaPropagation.MANDATORY)
+                .setBody(constant("failure"));
+
+        // ----------------- manual ----------------------------------
+
+        from("direct:manualSaga")
+                .saga()
+                .completionMode(SagaCompletionMode.MANUAL)
+                .completion("seda:manualSagaComplete")
+                .to("seda:manualSagaProcessOrder");
+
+        from("seda:manualSagaProcessOrder") // an asynchronous callback
+                .saga()
+                .propagation(SagaPropagation.MANDATORY)
+                .log("Processing manual saga order with complete set to 
${header.shouldComplete}")
+                .choice()
+                .when(header("shouldComplete").isEqualTo("true"))
+                .to("saga:complete") // complete the current saga manually 
(saga component)
+                .end();
+
+        from("seda:manualSagaComplete") // an asynchronous callback
+                .log("Manual saga marked as completed")
+                .bean(lraService, "complete");
+
     }
 }
diff --git 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraCreditService.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraCreditService.java
new file mode 100644
index 0000000000..2f10075711
--- /dev/null
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraCreditService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.saga.it.lra;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.Header;
+
+@ApplicationScoped
+@RegisterForReflection
+public class LraCreditService {
+
+    private int totalCredit;
+
+    private Map<String, Integer> reservations = new HashMap<>();
+
+    public LraCreditService() {
+        this.totalCredit = 100;
+    }
+
+    public synchronized void reserveCredit(@Header("Long-Running-Action") 
String id, @Header("amount") int amount) {
+        int credit = getCredit();
+        if (amount > credit) {
+            throw new IllegalStateException("Insufficient credit");
+        }
+        if (reservations.containsKey(id)) {
+            reservations.put(id, reservations.get(id) + amount);
+        } else {
+            reservations.put(id, amount);
+        }
+    }
+
+    public synchronized void refundCredit(@Header("Long-Running-Action") 
String id) {
+        reservations.remove(id);
+    }
+
+    public synchronized int getCredit() {
+        return totalCredit - reservations.values().stream().reduce(0, (a, b) 
-> a + b);
+    }
+
+    public void setTotalCredit(int totalCredit) {
+        this.totalCredit = totalCredit;
+    }
+}
diff --git 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraService.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraService.java
new file mode 100644
index 0000000000..c51b98a9ef
--- /dev/null
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraService.java
@@ -0,0 +1,58 @@
+/*
+ * 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.saga.it.lra;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
+import org.apache.camel.Header;
+
+@ApplicationScoped
+@Named("lraService")
+@RegisterForReflection
+public class LraService {
+
+    private Boolean completed, xmlCompleted;
+
+    public void sleep(@Header("timeout") long timeout) throws 
InterruptedException {
+        Thread.sleep(timeout);
+    }
+
+    public void setCompleted(boolean completed) {
+        this.completed = completed;
+    }
+
+    public void setXmlCompleted(boolean xmlCompleted) {
+        this.xmlCompleted = xmlCompleted;
+    }
+
+    public boolean isCompleted() {
+        return completed;
+    }
+
+    public void complete() {
+        this.completed = true;
+    }
+
+    public void xmlComplete() {
+        this.xmlCompleted = true;
+    }
+
+    public boolean isXmlCompleted() {
+        return xmlCompleted;
+    }
+}
diff --git 
a/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketService.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketService.java
new file mode 100644
index 0000000000..b68969c929
--- /dev/null
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketService.java
@@ -0,0 +1,70 @@
+/*
+ * 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.saga.it.lra;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@RegisterForReflection
+public class LraTicketService {
+
+    private LraTicketServiceStatus train = LraTicketServiceStatus.nothing;
+
+    private LraTicketServiceStatus flight = LraTicketServiceStatus.nothing;
+
+    public LraTicketServiceStatus getTrain() {
+        return train;
+    }
+
+    public void setTrainError() {
+        this.train = LraTicketServiceStatus.error;
+    }
+
+    public void setFlightError() {
+        this.flight = LraTicketServiceStatus.error;
+    }
+
+    public LraTicketServiceStatus getFlight() {
+        return flight;
+    }
+
+    public void setTicketsRefunded() {
+        //all tickets without error are refunded now
+        if (this.flight == LraTicketServiceStatus.nothing) {
+            this.flight = LraTicketServiceStatus.refunded;
+        }
+        if (this.train == LraTicketServiceStatus.nothing) {
+            this.train = LraTicketServiceStatus.refunded;
+        }
+    }
+
+    public void setTicketsReserved() {
+        //all tickets without error are refunded now
+        if (this.flight == LraTicketServiceStatus.nothing) {
+            this.flight = LraTicketServiceStatus.reserved;
+        }
+        if (this.train == LraTicketServiceStatus.nothing) {
+            this.train = LraTicketServiceStatus.reserved;
+        }
+    }
+
+    public void reset() {
+        this.train = LraTicketServiceStatus.nothing;
+        this.flight = LraTicketServiceStatus.nothing;
+    }
+}
diff --git 
a/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketServiceStatus.java
similarity index 59%
copy from 
integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
copy to 
integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketServiceStatus.java
index 9a382d982a..31fc5d256f 100644
--- 
a/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
+++ 
b/integration-tests/saga/src/main/java/org/apache/camel/quarkus/component/saga/it/lra/LraTicketServiceStatus.java
@@ -14,28 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.quarkus.component.saga.it;
-
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
-import org.junit.jupiter.api.Test;
-
-@QuarkusTest
-class SagaTest {
-
-    @Test
-    public void loadComponentSaga() {
-        /* A simple autogenerated test */
-        RestAssured.get("/saga/load/component/saga")
-                .then()
-                .statusCode(200);
-    }
-
-    @Test
-    public void testCreditExhausted() {
-        RestAssured.get("/saga/test")
-                .then()
-                .statusCode(200);
-    }
+package org.apache.camel.quarkus.component.saga.it.lra;
 
+public enum LraTicketServiceStatus {
+    nothing, //credit is enough
+    refunded, //the other ticket failed, therefore current ticket was refunded
+    error, //purchase of ticket failed due to insufficient credit
+    reserved;
 }
diff --git a/integration-tests/saga/src/main/resources/application.properties 
b/integration-tests/saga/src/main/resources/application.properties
new file mode 100644
index 0000000000..af4a30346b
--- /dev/null
+++ b/integration-tests/saga/src/main/resources/application.properties
@@ -0,0 +1,19 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+quarkus.artemis.devservices.extra-args=--no-autotune --mapped --no-fsync 
--java-options=-Dbrokerconfig.maxDiskUsage=-1
+quarkus.artemis.enabled=true
+camel.main.routes-include-pattern = routes/saga-routes.xml
\ No newline at end of file
diff --git a/integration-tests/saga/src/main/resources/routes/saga-routes.xml 
b/integration-tests/saga/src/main/resources/routes/saga-routes.xml
new file mode 100644
index 0000000000..49306533e4
--- /dev/null
+++ b/integration-tests/saga/src/main/resources/routes/saga-routes.xml
@@ -0,0 +1,54 @@
+<?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.
+
+-->
+<routes>
+    <route>
+        <from uri="direct:xmlSaga"/>
+        <saga>
+            <compensation uri="direct:xmlCompensation" />
+            <completion uri="direct:xmlCompletion" />
+            <option key="shouldComplete">
+                <header>complete</header>
+            </option>
+        </saga>
+        <to uri="direct:xmlAction" />
+    </route>
+
+    <route>
+        <from uri="direct:xmlAction" />
+        <log message="Xml action" />
+        <choice>
+            <when>
+                <simple>${header.complete} == false</simple>
+                <throwException exceptionType="java.lang.RuntimeException" 
message="Intended xml exception"/>
+            </when>
+        </choice>
+    </route>
+
+    <route>
+        <from uri="direct:xmlCompensation" />
+        <log message="Xml compensation" />
+    </route>
+
+    <route>
+        <from uri="direct:xmlCompletion" />
+        <log message="Xml completion" />
+        <to uri="bean:lraService?method=xmlComplete"/>
+    </route>
+</routes>
diff --git 
a/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
 
b/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
index 9a382d982a..d45427e066 100644
--- 
a/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
+++ 
b/integration-tests/saga/src/test/java/org/apache/camel/quarkus/component/saga/it/SagaTest.java
@@ -16,8 +16,13 @@
  */
 package org.apache.camel.quarkus.component.saga.it;
 
+import java.util.concurrent.TimeUnit;
+
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
+import org.apache.camel.quarkus.component.saga.it.lra.LraTicketServiceStatus;
+import org.awaitility.Awaitility;
+import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Test;
 
 @QuarkusTest
@@ -38,4 +43,131 @@ class SagaTest {
                 .statusCode(200);
     }
 
+    //long-run-actions using jms.
+    // Scenario - buying train and flight ticket.
+    //            If credit is not sufficient, the purchase fails.
+    //            All payments are refunded, the reason of refundment is saved 
in ticket service
+    //
+    // rest endpoint  saga/lraSaga/1/100/50/50 has following attributes: 
#orderId/#initialCredit/#trabCost/#flightCost
+
+    @Test
+    public void testLRACompletedScenario() {
+        //successful transaction
+        RestAssured.get("/saga/lraSaga/1/100/50/50")
+                .then()
+                .statusCode(200)
+                .body("creditBalance", Matchers.is(0))
+                .body("train", 
Matchers.is(LraTicketServiceStatus.reserved.name()))
+                .body("flight", 
Matchers.is(LraTicketServiceStatus.reserved.name()));
+    }
+
+    @Test
+    public void testLRAInsufficientCredit01() {
+        //successful transaction
+        RestAssured.get("/saga/lraSaga/2/50/50/100")
+                .then()
+                .statusCode(500)
+                .body("creditBalance", Matchers.is(50))
+                .body("train", 
Matchers.is(LraTicketServiceStatus.refunded.name()))
+                .body("flight", 
Matchers.is(LraTicketServiceStatus.error.name()));
+    }
+
+    @Test
+    public void testLRAInsufficientCredit02() {
+        //successful transaction
+        RestAssured.get("/saga/lraSaga/3/50/100/50")
+                .then()
+                .statusCode(500)
+                .body("creditBalance", Matchers.is(50))
+                .body("train", 
Matchers.is(LraTicketServiceStatus.error.name()))
+                .body("flight", 
Matchers.is(LraTicketServiceStatus.refunded.name()));
+    }
+
+    @Test
+    public void testLRAInsufficientCredit03() {
+        //successful transaction
+        RestAssured.get("/saga/lraSaga/4/50/100/100")
+                .then()
+                .statusCode(500)
+                .body("creditBalance", Matchers.is(50))
+                .body("train", 
Matchers.is(LraTicketServiceStatus.error.name()))
+                .body("flight", 
Matchers.is(LraTicketServiceStatus.refunded.name())); //bought of flight ticket 
is not attempted
+    }
+
+    @Test
+    public void testLRAInsufficientCredit04() {
+        //successful transaction
+        RestAssured.get("/saga/lraSaga/5/50/50/50")
+                .then()
+                .statusCode(500)
+                .body("creditBalance", Matchers.is(50))
+                .body("train", 
Matchers.is(LraTicketServiceStatus.refunded.name()))
+                .body("flight", 
Matchers.is(LraTicketServiceStatus.error.name())); //the second buy action fails
+    }
+
+    @Test
+    public void testTimeoutSuccessful() {
+        //successful transaction
+        RestAssured.get("/saga/timeout/2000")
+                .then()
+                .body(Matchers.is("success"))
+                .statusCode(200);
+    }
+
+    @Test
+    public void testTimeoutFailure() {
+        //successful transaction
+        RestAssured.get("/saga/timeout/10000")
+                .then()
+                .statusCode(500);
+    }
+
+    @Test
+    public void testManualSuccess() {
+
+        //start saga action, which won't complete
+        RestAssured.given().get("/saga/manualSaga/true").then()
+                .statusCode(200);
+
+        Awaitility.await().pollInterval(1, TimeUnit.SECONDS).atMost(10, 
TimeUnit.MINUTES).untilAsserted(
+                () -> RestAssured.get("/saga/manualCompleted")
+                        .then()
+                        .statusCode(200)
+                        .body(Matchers.is("true")));
+
+    }
+
+    @Test
+    public void testManualFailure() throws InterruptedException {
+
+        //start saga action, which won't complete
+        RestAssured.given().get("/saga/manualSaga/false").then()
+                .statusCode(200);
+
+        //wait some time and the saga should not be completed
+        Thread.sleep(10000);
+
+        RestAssured.get("/saga/manualCompleted")
+                .then()
+                .statusCode(200)
+                .body(Matchers.is("false"));
+    }
+
+    @Test
+    public void testXmlDslSuccess() throws InterruptedException {
+
+        RestAssured.get("/saga/xmlSaga/true")
+                .then()
+                .statusCode(200)
+                .body(Matchers.is("true"));
+    }
+
+    @Test
+    public void testXmlDslFailure() throws InterruptedException {
+        RestAssured.get("/saga/xmlSaga/false")
+                .then()
+                .statusCode(500)
+                .body(Matchers.is("Intended xml exception"));
+    }
+
 }


Reply via email to