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

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

commit 9d4649a731d5bc17144bb7ed90fda5b7977e658a
Author: jomin mathew <>
AuthorDate: Tue Mar 24 12:10:55 2026 +0000

    Fixes #6195: Add integration tests for Saga example
---
 saga/README.adoc                                   |  25 +++-
 saga/pom.xml                                       |  19 ++-
 saga/saga-app/pom.xml                              |  17 +++
 .../org/apache/camel/example/saga/SagaRoute.java   |   9 +-
 saga/saga-flight-service/pom.xml                   |  17 +++
 .../org/apache/camel/example/saga/FlightRoute.java |  10 +-
 saga/saga-integration-tests/README.md              | 126 ++++++++++++++++++
 saga/saga-integration-tests/pom.xml                | 148 +++++++++++++++++++++
 .../org/apache/camel/example/saga/SagaBasicIT.java |  28 ++++
 .../apache/camel/example/saga/SagaBasicTest.java   | 106 +++++++++++++++
 .../camel/example/saga/SagaTestResource.java       | 115 ++++++++++++++++
 .../src/test/resources/application.yml             |  48 +++++++
 saga/saga-payment-service/pom.xml                  |  17 +++
 .../apache/camel/example/saga/PaymentRoute.java    |   4 +-
 saga/saga-train-service/pom.xml                    |  17 +++
 .../org/apache/camel/example/saga/TrainRoute.java  |  10 +-
 16 files changed, 697 insertions(+), 19 deletions(-)

diff --git a/saga/README.adoc b/saga/README.adoc
index d6dabf57..8a7ade08 100644
--- a/saga/README.adoc
+++ b/saga/README.adoc
@@ -8,12 +8,16 @@ and other general information.
 
 === How it works
 
-There are 4 services as participants of the Saga:
+There are 5 modules in this example:
 
-- payment-service: it emulates a real payment transaction, and it will be used 
by both flight-service and train-service
+**Service Modules:**
+- app: is the starting point, and it emulates a user that starts the 
transaction to buy both flight and train tickets
 - flight-service: it emulates the booking of a flight ticket, and it uses the 
payment-service to execute a payment transaction
 - train-service: it emulates the reservation of a train seat, and it uses the 
payment-service to execute a payment transaction
-- app: is the starting point, and it emulates a user that starts the 
transaction to buy both flight and train tickets
+- payment-service: it emulates a real payment transaction, and it will be used 
by both flight-service and train-service
+
+**Test Module:**
+- integration-tests: contains automated integration tests using Testcontainers 
for Docker-based testing
 
 The starting point is a REST endpoint that creates a request for a new 
reservation
 and there is 15% probability that the payment service fails.
@@ -101,6 +105,21 @@ Transaction 
http://localhost:8080/lra-coordinator/0_ffff7f000001_8aad_62d16f11_7
 ----
 
 
+=== Running Tests
+
+The integration tests use Testcontainers to automatically start Artemis and 
LRA Coordinator in Docker.
+
+[source,shell]
+----
+# Ensure Docker is running
+docker ps
+
+# Run integration tests
+mvn clean test -pl saga-integration-tests
+----
+
+See link:saga-integration-tests/README.md[saga-integration-tests/README.md] 
for more details.
+
 === Package and run the application
 
 Once you are done with developing you may want to package and run the 
application.
diff --git a/saga/pom.xml b/saga/pom.xml
index 932b4961..75d70b56 100644
--- a/saga/pom.xml
+++ b/saga/pom.xml
@@ -44,6 +44,7 @@
 
         <formatter-maven-plugin.version>2.29.0</formatter-maven-plugin.version>
         <impsort-maven-plugin.version>1.13.0</impsort-maven-plugin.version>
+        <jandex-maven-plugin.version>3.0.1</jandex-maven-plugin.version>
         <license-maven-plugin.version>5.0.0</license-maven-plugin.version>
         <maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
         <maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
@@ -56,6 +57,7 @@
         <module>saga-flight-service</module>
         <module>saga-payment-service</module>
         <module>saga-train-service</module>
+        <module>saga-integration-tests</module>
     </modules>
 
     <dependencyManagement>
@@ -87,6 +89,10 @@
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-bean</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-direct</artifactId>
@@ -104,13 +110,6 @@
             <artifactId>quarkus-artemis-jms</artifactId>
             <version>${quarkiverse-artemis.version}</version>
         </dependency>
-
-        <!-- Test -->
-        <dependency>
-            <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-junit</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <build>
@@ -182,6 +181,12 @@
                     <version>${maven-jar-plugin.version}</version>
                 </plugin>
 
+                <plugin>
+                    <groupId>io.smallrye</groupId>
+                    <artifactId>jandex-maven-plugin</artifactId>
+                    <version>${jandex-maven-plugin.version}</version>
+                </plugin>
+
                 <plugin>
                     <groupId>com.mycila</groupId>
                     <artifactId>license-maven-plugin</artifactId>
diff --git a/saga/saga-app/pom.xml b/saga/saga-app/pom.xml
index b7fd20c3..ad10c7b7 100644
--- a/saga/saga-app/pom.xml
+++ b/saga/saga-app/pom.xml
@@ -31,4 +31,21 @@
     <name>Camel Quarkus :: Examples :: Saga :: App</name>
     <description>Main application starting SAGA</description>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.smallrye</groupId>
+                <artifactId>jandex-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>make-index</id>
+                        <goals>
+                            <goal>jandex</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git 
a/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java 
b/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java
index d1985326..6f31b1df 100644
--- a/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java
+++ b/saga/saga-app/src/main/java/org/apache/camel/example/saga/SagaRoute.java
@@ -16,9 +16,11 @@
  */
 package org.apache.camel.example.saga;
 
+import jakarta.enterprise.context.ApplicationScoped;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.model.rest.RestParamType;
 
+@ApplicationScoped
 public class SagaRoute extends RouteBuilder {
 
     @Override
@@ -33,12 +35,15 @@ public class SagaRoute extends RouteBuilder {
                 .compensation("direct:cancelOrder")
                 .log("Executing saga #${header.id} with LRA 
${header.Long-Running-Action}")
                 .setHeader("payFor", constant("train"))
+                // Request timeout prevents indefinite waits during service 
failures or slow responses
                 
.to("jms:queue:{{example.services.train}}?exchangePattern=InOut" +
-                        "&replyTo={{example.services.train}}.reply")
+                        "&replyTo={{example.services.train}}.reply" +
+                        "&requestTimeout=30000")
                 .log("train seat reserved for saga #${header.id} with payment 
transaction: ${body}")
                 .setHeader("payFor", constant("flight"))
                 
.to("jms:queue:{{example.services.flight}}?exchangePattern=InOut" +
-                        "&replyTo={{example.services.flight}}.reply")
+                        "&replyTo={{example.services.flight}}.reply" +
+                        "&requestTimeout=30000")
                 .log("flight booked for saga #${header.id} with payment 
transaction: ${body}")
                 .setBody(header("Long-Running-Action"))
                 .end();
diff --git a/saga/saga-flight-service/pom.xml b/saga/saga-flight-service/pom.xml
index 40cf31d0..62c36c58 100644
--- a/saga/saga-flight-service/pom.xml
+++ b/saga/saga-flight-service/pom.xml
@@ -31,4 +31,21 @@
     <name>Camel Quarkus :: Examples :: Saga :: Flight Service</name>
     <description>Flight Service</description>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.smallrye</groupId>
+                <artifactId>jandex-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>make-index</id>
+                        <goals>
+                            <goal>jandex</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git 
a/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java
 
b/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java
index 8e0bd7e0..04694f18 100644
--- 
a/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java
+++ 
b/saga/saga-flight-service/src/main/java/org/apache/camel/example/saga/FlightRoute.java
@@ -16,9 +16,11 @@
  */
 package org.apache.camel.example.saga;
 
+import jakarta.enterprise.context.ApplicationScoped;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.model.SagaPropagation;
 
+@ApplicationScoped
 public class FlightRoute extends RouteBuilder {
 
     @Override
@@ -27,14 +29,16 @@ public class FlightRoute extends RouteBuilder {
                 .saga()
                 .propagation(SagaPropagation.MANDATORY)
                 .option("id", header("id"))
-                .compensation("direct:cancelPurchase")
+                .compensation("direct:cancelFlightPurchase")
                 .log("Buying flight #${header.id}")
+                // Request timeout prevents indefinite waits during payment 
service failures
                 
.to("jms:queue:{{example.services.payment}}?exchangePattern=InOut" +
-                        "&replyTo={{example.services.payment}}.flight.reply")
+                        "&replyTo={{example.services.payment}}.flight.reply" +
+                        "&requestTimeout=30000")
                 .log("Payment for flight #${header.id} done with transaction 
${body}")
                 .end();
 
-        from("direct:cancelPurchase")
+        from("direct:cancelFlightPurchase")
                 .log("Flight purchase #${header.id} has been cancelled due to 
payment failure");
     }
 
diff --git a/saga/saga-integration-tests/README.md 
b/saga/saga-integration-tests/README.md
new file mode 100644
index 00000000..86a3f139
--- /dev/null
+++ b/saga/saga-integration-tests/README.md
@@ -0,0 +1,126 @@
+# Saga Integration Tests
+
+Integration tests for the Camel Quarkus Saga example demonstrating distributed 
transaction coordination using the LRA (Long Running Action) pattern with JMS 
messaging.
+
+## Overview
+
+This module tests the Saga example by running all services (train, flight, 
payment) in a single JVM with Testcontainers managing external dependencies 
(LRA Coordinator and Artemis broker).
+
+## Test Coverage
+
+The test suite verifies:
+
+- **LRA Integration:** Saga coordination with LRA coordinator
+- **JMS Messaging:** Request-reply pattern over Artemis queues
+- **Service Participation:** Train, flight, and payment service coordination
+- **Compensation Flow:** Rollback when failures occur (15% random failure rate)
+- **End-to-End Flow:** Complete saga orchestration
+
+### Test Case
+
+`testSagaWithLRAAndRandomOutcomes()` - Comprehensive end-to-end test that 
verifies the complete saga flow including LRA coordination, all service 
participation (train, flight, payment), and validates both success and 
compensation scenarios. Since the payment service has a 15% random failure 
rate, the test accepts either outcome as valid.
+
+## Running Tests
+
+### Prerequisites
+
+- Java 17+
+- Maven 3.9+
+- Docker (for Testcontainers)
+
+### Execute Tests
+
+```bash
+# Run all tests
+mvn clean test -pl saga-integration-tests
+
+# Run specific test
+mvn test -pl saga-integration-tests -Dtest=SagaBasicTest#testCompleteSagaFlow
+
+# Native mode
+mvn clean verify -Pnative -pl saga-integration-tests
+```
+
+## Infrastructure
+
+**Testcontainers manages:**
+
+- **LRA Coordinator** (`quay.io/jbosstm/lra-coordinator:latest`) - Distributed 
saga coordination
+- **Artemis Broker** (`quay.io/artemiscloud/activemq-artemis-broker:latest`) - 
JMS messaging
+
+Both containers run on a shared Docker network with proper wait strategies.
+
+## Configuration
+
+### Test Settings (`src/test/resources/application.yml`)
+
+Key configuration settings:
+
+```yaml
+quarkus:
+  http:
+    port: 8084
+  log:
+    file:
+      enable: true
+      path: target/quarkus.log
+    category:
+      "org.apache.camel": DEBUG
+      "org.apache.camel.saga": DEBUG
+      "org.apache.camel.component.lra": DEBUG
+
+camel:
+  lra:
+    enabled: true
+    # coordinator-url and local-participant-url are set by SagaTestResource
+  component:
+    jms:
+      test-connection-on-startup: true
+      concurrent-consumers: 5
+```
+
+Dynamic configuration (Artemis URL, LRA coordinator URL) is injected by 
`SagaTestResource` at test runtime.
+
+## Saga Flow
+
+```
+POST /api/saga?id=1
+  → SagaRoute creates LRA transaction
+  → Sends to jms:queue:saga-train-service
+    → TrainRoute processes and sends to jms:queue:saga-payment-service
+    → PaymentRoute completes payment
+  → Sends to jms:queue:saga-flight-service
+    → FlightRoute processes and sends to jms:queue:saga-payment-service
+    → PaymentRoute completes payment
+  → LRA Coordinator commits saga
+  → Returns LRA ID
+```
+
+## Troubleshooting
+
+### Tests Fail with "Connection Refused"
+
+Docker not running. Start Docker and verify:
+```bash
+docker ps
+```
+
+### Tests Timeout
+
+Increase timeout in tests:
+```java
+await().atMost(30, TimeUnit.SECONDS)  // Instead of 10-15
+```
+
+### View Container Logs
+
+```bash
+docker ps  # Find container ID
+docker logs <container-id>
+```
+
+## Related Links
+
+- [Camel Saga 
EIP](https://camel.apache.org/components/latest/eips/saga-eip.html)
+- [Camel LRA 
Component](https://camel.apache.org/components/latest/lra-component.html)
+- [Issue #6195](https://github.com/apache/camel-quarkus/issues/6195)
diff --git a/saga/saga-integration-tests/pom.xml 
b/saga/saga-integration-tests/pom.xml
new file mode 100644
index 00000000..e74983de
--- /dev/null
+++ b/saga/saga-integration-tests/pom.xml
@@ -0,0 +1,148 @@
+<?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";>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>camel-quarkus-examples-saga</artifactId>
+        <groupId>org.apache.camel.quarkus.examples</groupId>
+        <version>3.35.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>camel-quarkus-example-saga-integration-tests</artifactId>
+    <name>Camel Quarkus :: Examples :: Saga :: Integration Tests</name>
+    <description>Integration tests for Saga example</description>
+
+    <dependencies>
+        <!-- Saga app dependencies - includes routes and configurations -->
+        <dependency>
+            <groupId>org.apache.camel.quarkus.examples</groupId>
+            <artifactId>camel-quarkus-example-saga-app</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus.examples</groupId>
+            <artifactId>camel-quarkus-example-saga-flight</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus.examples</groupId>
+            <artifactId>camel-quarkus-example-saga-train</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus.examples</groupId>
+            <artifactId>camel-quarkus-example-saga-payment</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build</id>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <trimStackTrace>true</trimStackTrace>
+                    <systemPropertyVariables>
+                        
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <quarkus.native.enabled>true</quarkus.native.enabled>
+            </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>
+        <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/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java
 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java
new file mode 100644
index 00000000..c27b6790
--- /dev/null
+++ 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicIT.java
@@ -0,0 +1,28 @@
+/*
+ * 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.example.saga;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+/**
+ * Integration test for native mode compilation.
+ * Extends SagaBasicTest to run the same tests against native executable.
+ */
+@QuarkusIntegrationTest
+public class SagaBasicIT extends SagaBasicTest {
+    // Runs all tests from SagaBasicTest in native mode
+}
diff --git 
a/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java
 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java
new file mode 100644
index 00000000..64700c51
--- /dev/null
+++ 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaBasicTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.example.saga;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.awaitility.Awaitility;
+import org.jboss.logmanager.Logger;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Basic integration tests for Saga example.
+ * Tests verify saga orchestration, service participation, routing, and 
compensation.
+ * Note: Payment service has 15% random failure rate to test compensation 
scenarios.
+ */
+@QuarkusTest
+@QuarkusTestResource(SagaTestResource.class)
+public class SagaBasicTest {
+    private static final String LOG_FILE = "target/quarkus.log";
+    private static final Logger LOG = 
Logger.getLogger(SagaBasicTest.class.getName());
+
+    /**
+     * Test saga orchestration with LRA - accepts both success and 
compensation outcomes.
+     * Payment service has 15% random failure rate, so either scenario is 
valid.
+     */
+    @Test
+    public void testSagaWithLRAAndRandomOutcomes() throws Exception {
+        // Trigger saga asynchronously (may timeout on payment failure, which 
is expected)
+        CompletableFuture.runAsync(() -> {
+            try {
+                RestAssured.given()
+                        .queryParam("id", 1)
+                        .post("/api/saga");
+            } catch (Exception e) {
+                // Expected - request may timeout on payment failure
+            }
+        });
+
+        // Wait for saga to start and process fully
+        // In native mode, we need to wait longer for all messages to be logged
+        await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> {
+            String log = Files.readString(Paths.get(LOG_FILE));
+            assertTrue(log.contains("Executing saga #1"), "Saga should start 
with LRA");
+
+            // Wait until we see evidence of completion (success or failure)
+            boolean completed = log.contains("done for order #1")
+                    || log.contains("fails!")
+                    || log.contains("cancelled");
+            assertTrue(completed, "Saga should complete with either success or 
compensation");
+        });
+
+        Awaitility.await()
+                .pollDelay(Duration.ofSeconds(1))
+                .pollInterval(Duration.ofMillis(250))
+                .atMost(Duration.ofMinutes(1))
+                .untilAsserted(() -> {
+                    String log = Files.readString(Paths.get(LOG_FILE));
+
+                    // Verify LRA coordinator is used
+                    assertTrue(log.contains("lra-coordinator"), "Should use 
LRA coordinator");
+
+                    // Verify services participated
+                    assertTrue(log.contains("Buying train") || 
log.contains("Buying flight"),
+                            "Services should participate");
+
+                    // Check outcome - either success or compensation is valid
+                    boolean hasSuccess = log.contains("done for order #1");
+                    boolean hasFailure = log.contains("fails!");
+                    boolean hasCompensation = log.contains("cancelled");
+
+                    if (hasFailure || hasCompensation) {
+                        LOG.info("Saga #1: Compensation scenario tested 
(payment failed, saga rolled back)");
+                    } else if (hasSuccess) {
+                        LOG.info("Saga #1: Success scenario tested (all 
payments completed)");
+                    }
+
+                    // Either outcome is valid because the trigger for saga 
compensation is triggered at random
+                    assertTrue(hasSuccess || hasFailure,
+                            "Saga should complete with either success or 
compensation");
+                });
+    }
+}
diff --git 
a/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java
 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java
new file mode 100644
index 00000000..b2fa0000
--- /dev/null
+++ 
b/saga/saga-integration-tests/src/test/java/org/apache/camel/example/saga/SagaTestResource.java
@@ -0,0 +1,115 @@
+/*
+ * 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.example.saga;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.commons.lang3.SystemUtils;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+/**
+ * Testcontainers resource for Saga integration tests.
+ * Manages the lifecycle of LRA Coordinator and Artemis broker.
+ */
+public class SagaTestResource implements QuarkusTestResourceLifecycleManager {
+
+    private static final String LRA_IMAGE = 
"quay.io/jbosstm/lra-coordinator:7.0.1.Final-3.8.3";
+    private static final String ARTEMIS_IMAGE = 
"quay.io/arkmq-org/activemq-artemis-broker:artemis.2.51.0";
+    private static final int LRA_PORT = 8080;
+    private static final int ARTEMIS_PORT = 61616;
+
+    private GenericContainer<?> lraContainer;
+    private GenericContainer<?> artemisContainer;
+    private Network network;
+
+    @Override
+    public Map<String, String> start() {
+        network = Network.newNetwork();
+
+        // Start Artemis broker
+        artemisContainer = new GenericContainer<>(ARTEMIS_IMAGE)
+                .withNetwork(network)
+                .withNetworkAliases("artemis")
+                .withEnv("AMQ_USER", "admin")
+                .withEnv("AMQ_PASSWORD", "admin")
+                .withExposedPorts(ARTEMIS_PORT)
+                .waitingFor(Wait.forListeningPort()
+                        .withStartupTimeout(Duration.ofSeconds(60)));
+
+        artemisContainer.start();
+
+        // Start LRA Coordinator
+        lraContainer = new GenericContainer<>(LRA_IMAGE)
+                .withNetwork(network)
+                .withNetworkAliases("lra-coordinator")
+                .withEnv("QUARKUS_HTTP_PORT", String.valueOf(LRA_PORT))
+                .withExposedPorts(LRA_PORT)
+                .withExtraHost("host.testcontainers.internal", "host-gateway")
+                .waitingFor(Wait.forHttp("/lra-coordinator")
+                        .forPort(LRA_PORT)
+                        .forStatusCode(200)
+                        .withStartupTimeout(Duration.ofSeconds(90)));
+
+        if (!SystemUtils.IS_OS_LINUX) {
+            lraContainer.withNetworkMode("bridge");
+        }
+
+        lraContainer.start();
+
+        Map<String, String> config = new HashMap<>();
+
+        // Artemis configuration
+        String artemisUrl = String.format("tcp://%s:%d",
+                artemisContainer.getHost(),
+                artemisContainer.getMappedPort(ARTEMIS_PORT));
+        config.put("quarkus.artemis.url", artemisUrl);
+        config.put("quarkus.artemis.username", "admin");
+        config.put("quarkus.artemis.password", "admin");
+
+        // LRA configuration
+        String lraCoordinatorUrl = String.format("http://%s:%d";,
+                lraContainer.getHost(),
+                lraContainer.getMappedPort(LRA_PORT));
+        config.put("camel.lra.coordinator-url", lraCoordinatorUrl);
+
+        // Set local participant URL - use host.testcontainers.internal for 
coordinator callbacks
+        config.put("camel.lra.local-participant-url", 
"http://host.testcontainers.internal:8084/api";);
+
+        // Allow external connections
+        config.put("quarkus.http.host", "0.0.0.0");
+
+        return config;
+    }
+
+    @Override
+    public void stop() {
+        if (artemisContainer != null) {
+            artemisContainer.stop();
+        }
+        if (lraContainer != null) {
+            lraContainer.stop();
+        }
+        if (network != null) {
+            network.close();
+        }
+    }
+}
diff --git a/saga/saga-integration-tests/src/test/resources/application.yml 
b/saga/saga-integration-tests/src/test/resources/application.yml
new file mode 100644
index 00000000..da44b5a2
--- /dev/null
+++ b/saga/saga-integration-tests/src/test/resources/application.yml
@@ -0,0 +1,48 @@
+#
+# 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.
+#
+
+# Test configuration
+# External dependencies (Artemis, LRA) are managed by SagaTestResource
+
+quarkus:
+  http:
+    port: 8084
+  log:
+    console:
+      format: "%d{HH:mm:ss} %-5p [%c{2.}] %s%e%n"
+    level: INFO
+    min-level: DEBUG
+    file:
+      enable: true
+      path: target/quarkus.log
+
+camel:
+  rest:
+    context-path: /api
+  component:
+    jms:
+      test-connection-on-startup: true
+      concurrent-consumers: 5
+  lra:
+    enabled: true
+    # coordinator-url and local-participant-url are set by SagaTestResource
+
+example:
+  services:
+    train: saga-train-service
+    flight: saga-flight-service
+    payment: saga-payment-service
diff --git a/saga/saga-payment-service/pom.xml 
b/saga/saga-payment-service/pom.xml
index 83d7b951..0661d46d 100644
--- a/saga/saga-payment-service/pom.xml
+++ b/saga/saga-payment-service/pom.xml
@@ -31,4 +31,21 @@
     <name>Camel Quarkus :: Examples :: Saga :: Payment Service</name>
     <description>Payment Service</description>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.smallrye</groupId>
+                <artifactId>jandex-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>make-index</id>
+                        <goals>
+                            <goal>jandex</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git 
a/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java
 
b/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java
index 342a3d87..3b2fd20b 100644
--- 
a/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java
+++ 
b/saga/saga-payment-service/src/main/java/org/apache/camel/example/saga/PaymentRoute.java
@@ -16,9 +16,11 @@
  */
 package org.apache.camel.example.saga;
 
+import jakarta.enterprise.context.ApplicationScoped;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.model.SagaPropagation;
 
+@ApplicationScoped
 public class PaymentRoute extends RouteBuilder {
 
     @Override
@@ -33,7 +35,7 @@ public class PaymentRoute extends RouteBuilder {
                 .log("Paying ${header.payFor} for order #${header.id}")
                 .setBody(header("JMSCorrelationID"))
                 .choice()
-                .when(x -> Math.random() >= 0.85)
+                .when(simple("${random(0,100)} >= 85"))
                 .log("Payment ${header.payFor} for saga #${header.id} fails!")
                 .throwException(new RuntimeException("Random failure during 
payment"))
                 .endChoice()
diff --git a/saga/saga-train-service/pom.xml b/saga/saga-train-service/pom.xml
index 5af427e8..ef7da71b 100644
--- a/saga/saga-train-service/pom.xml
+++ b/saga/saga-train-service/pom.xml
@@ -31,4 +31,21 @@
     <name>Camel Quarkus :: Examples :: Saga :: Train Service</name>
     <description>Train Service</description>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.smallrye</groupId>
+                <artifactId>jandex-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>make-index</id>
+                        <goals>
+                            <goal>jandex</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>
diff --git 
a/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java
 
b/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java
index bf5b05a6..ef339ef4 100644
--- 
a/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java
+++ 
b/saga/saga-train-service/src/main/java/org/apache/camel/example/saga/TrainRoute.java
@@ -16,9 +16,11 @@
  */
 package org.apache.camel.example.saga;
 
+import jakarta.enterprise.context.ApplicationScoped;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.model.SagaPropagation;
 
+@ApplicationScoped
 public class TrainRoute extends RouteBuilder {
 
     @Override
@@ -27,14 +29,16 @@ public class TrainRoute extends RouteBuilder {
                 .saga()
                 .propagation(SagaPropagation.MANDATORY)
                 .option("id", header("id"))
-                .compensation("direct:cancelPurchase")
+                .compensation("direct:cancelTrainPurchase")
                 .log("Buying train #${header.id}")
+                // Request timeout prevents indefinite waits during payment 
service failures
                 
.to("jms:queue:{{example.services.payment}}?exchangePattern=InOut" +
-                        "&replyTo={{example.services.payment}}.train.reply")
+                        "&replyTo={{example.services.payment}}.train.reply" +
+                        "&requestTimeout=30000")
                 .log("Payment for train #${header.id} done with transaction 
${body}")
                 .end();
 
-        from("direct:cancelPurchase")
+        from("direct:cancelTrainPurchase")
                 .log("Train purchase #${header.id} has been cancelled due to 
payment failure");
     }
 


Reply via email to