Skip to content

Commit 2dd49f2

Browse files
authored
Merge pull request #1179 from jmfinelli/WFLY-20952
[WFLY-20952] Added integration tests to the JTS quickstart
2 parents 28ccb98 + dac81cc commit 2dd49f2

11 files changed

Lines changed: 390 additions & 26 deletions

File tree

jts/README-source.adoc

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ After stopping the server, open the `__{jbossHomeName}_1__/standalone/configurat
114114
+
115115
[source,xml,subs="attributes+",options="nowrap"]
116116
----
117-
<subsystem xmlns="{IiopOpenJdkSubsystemNamespace}">
117+
<subsystem xmlns="urn:jboss:domain:iiop-openjdk:3.0">
118118
<initializers security="elytron" transactions="full" />
119119
</subsystem>
120120
----
@@ -123,7 +123,7 @@ After stopping the server, open the `__{jbossHomeName}_1__/standalone/configurat
123123
+
124124
[source,xml,subs="attributes+",options="nowrap"]
125125
----
126-
<subsystem xmlns="{TransactionsSubsystemNamespace}">
126+
<subsystem xmlns="urn:jboss:domain:transactions:7.0">
127127
<core-environment node-identifier="${jboss.tx.node.id}">
128128
<process-id>
129129
<uuid/>
@@ -143,32 +143,30 @@ Make a copy of this {productName} directory structure to use for the second serv
143143
=== Configure Server1 to use PostgreSQL
144144

145145
The {productName} server 1 need to be configured to be able to connect to the database created previously in the xref:configure_the_postgresql_database_for_use_with_this_quickstart[Create a PostgreSQL Database] section.
146-
First of all, a JDBC driver needs to be installed as https://docs.wildfly.org/30/Developer_Guide.html#Class_Loading_in_WildFly[jboss module].
147-
148-
The following command downloads the PostgreSQL driver automatically through Maven. When needed, replace __{jbossHomeName}_1__ with the path to your server
146+
First of all, a JDBC driver needs to be installed. The following command downloads the PostgreSQL driver automatically through Maven. When needed, replace __{jbossHomeName}_1__ with the path to your server
149147

150148
1. Open a new terminal, navigate to the root directory of this quickstart, and run the following command:
151149
[source,sh,subs="+quotes,attributes+",options="nowrap"]
152150
+
153151
----
154152
mvn clean package
155153
----
156-
1. Then, add a JBoss Module with the PostgreSQL driver to the {productName} server 1:
154+
2. Then, add a JBoss Module with the PostgreSQL driver to the {productName} server 1:
157155
+
158156
[source,sh,subs="+quotes,attributes+",options="nowrap"]
159157
----
160158
$__{jbossHomeName}_1__/bin/jboss-cli.sh "embed-server,\
161159
module add --name=org.postgresql.jdbc \
162160
--resources=application-component-1/target/postgresql/postgresql.jar"
163161
----
164-
1. Moreover, the PostgreSQL driver needs to be added to the datasources subsystem, in the {productName} server 1:
162+
3. Moreover, the PostgreSQL driver needs to be added to the datasources subsystem, in the {productName} server 1:
165163
+
166164
[source,sh,subs="+quotes,attributes+",options="nowrap"]
167165
----
168166
$__{jbossHomeName}_1__/bin/jboss-cli.sh "embed-server --server-config=standalone-full.xml,\
169167
/subsystem=datasources/jdbc-driver=postgresql:add(driver-name=postgresql,driver-module-name=org.postgresql.jdbc,driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource)"
170168
----
171-
1. Finally, it is time to run the script for adding the PostgreSQL datasource to the {productName} server 1:
169+
4. Finally, it is time to run the script for adding the PostgreSQL datasource to the {productName} server 1:
172170
+
173171
[source,sh,subs="+quotes,attributes+",options="nowrap"]
174172
----
@@ -205,7 +203,7 @@ Since this quickstart builds two separate components, you can not use the standa
205203
mvn clean install wildfly:deploy
206204
----
207205

208-
. This will deploy `{artifactId}-application-component-1.war` and `{artifactId}-application-component-2.jar` to the running instance of the server.
206+
. This will deploy `{artifactId}-application-component-1.war` and `{artifactId}-application-component-2.war` to the running instance of the server.
209207

210208
[NOTE]
211209
====
@@ -230,6 +228,32 @@ INFO [class org.jboss.as.quickstarts.cmt.jts.mdb.HelloWorldMDB] (Thread-97 (Act
230228

231229
The web page will also change and show you the new list of customers.
232230

231+
NOTE: You can also verify the state of the JMS queue on application server 2 by using the management CLI. Connect to the second server's management interface and run:
232+
233+
[source, subs="attributes+", options="nowrap"]
234+
----
235+
/deployment={artifactId}-application-component-2.war/subsystem=messaging-activemq/server=default/jms-queue=jts-quickstart:read-attribute(name=messages-added)
236+
----
237+
238+
This shows the total number of messages added to the queue since the server started.
239+
240+
// Server Distribution Testing
241+
include::../shared-doc/run-integration-tests-with-server-distribution.adoc[leveloffset=+1]
242+
243+
[NOTE]
244+
====
245+
When running the integration tests, the `testDuplicateCustomerRollback` test intentionally submits a duplicate customer name to verify the JTS distributed transaction rollback. The following warnings and errors will appear in the application server 1 log and are safe to ignore:
246+
[source]
247+
----
248+
WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-3) SQL Error: 0, SQLState: 23505
249+
ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-3) ERROR: duplicate key value violates unique constraint "uk6m4fuhxj4ckixi1vsb8osii7r"
250+
WARN [com.arjuna.ats.jts] (default task-3) ARJUNA022024: ArjunaTransactionImple.doBeforeCompletion caught exception: org.omg.CORBA.UNKNOWN: ...
251+
WARN [com.arjuna.ats.jtax] (default task-3) ARJUNA024068: Caught exception:: org.omg.CORBA.TRANSACTION_ROLLEDBACK: ...
252+
ERROR [org.jboss.as.ejb3.invocation] (default task-3) WFLYEJB0034: Jakarta Enterprise Beans Invocation failed on component CustomerManagerEJB ...
253+
WARNING [org.jboss.as.quickstarts.cmt.controller.CustomerManager] (default task-3) Caught a duplicate: ...
254+
----
255+
====
256+
233257
// Undeploy the Quickstart
234258
include::../shared-doc/undeploy-the-quickstart.adoc[leveloffset=+1]
235259

@@ -324,7 +348,7 @@ NOTE: For Windows, use the `__{jbossHomeName}_1__\bin\jboss-cli.bat` script.
324348
+
325349
[source,xml,subs="attributes+",options="nowrap"]
326350
----
327-
<subsystem xmlns="{IiopOpenJdkSubsystemNamespace}">
351+
<subsystem xmlns="urn:jboss:domain:iiop-openjdk:3.0">
328352
<initializers transactions="spec" security="identity"/>
329353
</subsystem>
330354
----
@@ -333,7 +357,7 @@ NOTE: For Windows, use the `__{jbossHomeName}_1__\bin\jboss-cli.bat` script.
333357
+
334358
[source,xml,subs="attributes+",options="nowrap"]
335359
----
336-
<subsystem xmlns="{TransactionsSubsystemNamespace}">
360+
<subsystem xmlns="urn:jboss:domain:transactions:7.0">
337361
<core-environment>
338362
<process-id>
339363
<uuid/>

jts/application-component-1/pom.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@
9090
<version>${version.postgresql}</version>
9191
<scope>provided</scope>
9292
</dependency>
93+
94+
<!-- Tests -->
95+
<dependency>
96+
<groupId>junit</groupId>
97+
<artifactId>junit</artifactId>
98+
<scope>test</scope>
99+
</dependency>
93100
</dependencies>
94101

95102
<build>
@@ -116,4 +123,31 @@
116123
</plugin>
117124
</plugins>
118125
</build>
126+
127+
<profiles>
128+
<profile>
129+
<id>integration-testing</id>
130+
<build>
131+
<plugins>
132+
<plugin>
133+
<groupId>org.apache.maven.plugins</groupId>
134+
<artifactId>maven-failsafe-plugin</artifactId>
135+
<configuration>
136+
<includes>
137+
<include>**/*IT</include>
138+
</includes>
139+
</configuration>
140+
<executions>
141+
<execution>
142+
<goals>
143+
<goal>integration-test</goal>
144+
<goal>verify</goal>
145+
</goals>
146+
</execution>
147+
</executions>
148+
</plugin>
149+
</plugins>
150+
</build>
151+
</profile>
152+
</profiles>
119153
</project>
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Copyright 2026 JBoss by Red Hat.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.jboss.as.quickstarts.cmt.jts;
17+
18+
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertTrue;
20+
21+
import java.io.IOException;
22+
import java.net.CookieManager;
23+
import java.net.CookiePolicy;
24+
import java.net.URI;
25+
import java.net.URISyntaxException;
26+
import java.net.URLEncoder;
27+
import java.net.http.HttpClient;
28+
import java.net.http.HttpRequest;
29+
import java.net.http.HttpRequest.BodyPublisher;
30+
import java.net.http.HttpRequest.BodyPublishers;
31+
import java.net.http.HttpResponse;
32+
import java.nio.charset.StandardCharsets;
33+
import java.time.Duration;
34+
import java.util.Map;
35+
import java.util.regex.Matcher;
36+
import java.util.regex.Pattern;
37+
38+
import org.junit.Test;
39+
40+
public class BasicRuntimeIT {
41+
42+
private static final String DEFAULT_SERVER_HOST = "http://localhost";
43+
private static final int DEFAULT_SERVER_PORT = 8080;
44+
private static final String APP_CONTEXT = "/jts-application-component-1";
45+
46+
@Test
47+
public void testHTTPEndpointIsAvailable() throws IOException, InterruptedException, URISyntaxException {
48+
String serverHost = getServerHost(0);
49+
final HttpRequest request = HttpRequest.newBuilder()
50+
.uri(new URI(serverHost + APP_CONTEXT + "/"))
51+
.GET()
52+
.build();
53+
final HttpClient client = HttpClient.newBuilder()
54+
.followRedirects(HttpClient.Redirect.ALWAYS)
55+
.connectTimeout(Duration.ofMinutes(1))
56+
.build();
57+
final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
58+
assertEquals(200, response.statusCode());
59+
assertTrue(response.body().contains(
60+
"<meta http-equiv=\"Refresh\" content=\"0; URL=addCustomer.jsf\">"));
61+
}
62+
63+
@Test
64+
public void testAddCustomer() throws Exception {
65+
HttpClient client = createHttpClient();
66+
String uniqueName = "TestCustomer_" + System.currentTimeMillis();
67+
long baselineMessages = getMessagesReceived();
68+
69+
HttpResponse<String> response = submitCustomerForm(client, uniqueName);
70+
assertEquals(200, response.statusCode());
71+
assertTrue(response.body().contains(uniqueName));
72+
assertTrue(response.uri().toString().contains("customers.jsf"));
73+
74+
waitForMessagesReceived(baselineMessages + 1, 30_000);
75+
}
76+
77+
@Test
78+
public void testDuplicateCustomerRollback() throws Exception {
79+
HttpClient client = createHttpClient();
80+
String uniqueName = "DuplicateTest_" + System.currentTimeMillis();
81+
long baselineMessages = getMessagesReceived();
82+
83+
HttpResponse<String> response = submitCustomerForm(client, uniqueName);
84+
assertEquals(200, response.statusCode());
85+
assertTrue(response.body().contains(uniqueName));
86+
assertTrue(response.uri().toString().contains("customers.jsf"));
87+
88+
waitForMessagesReceived(baselineMessages + 1, 30_000);
89+
90+
response = submitCustomerForm(client, uniqueName);
91+
assertEquals(200, response.statusCode());
92+
assertTrue(response.uri().toString().contains("duplicate.jsf"));
93+
94+
assertMessagesStayAt(baselineMessages + 1, 10_000);
95+
}
96+
97+
// SERVER_HOST is expected to include the port (e.g. http://localhost:8080).
98+
// When portOffset is non-zero, the port is parsed and the offset is added
99+
// (defaulting to 80 for http or 443 for https if no port is present).
100+
private String getServerHost(int portOffset) {
101+
String host = System.getenv("SERVER_HOST");
102+
if (host == null) {
103+
host = System.getProperty("server.host");
104+
}
105+
if (host == null) {
106+
host = DEFAULT_SERVER_HOST + ":" + (DEFAULT_SERVER_PORT + portOffset);
107+
} else if (portOffset != 0) {
108+
try {
109+
URI uri = new URI(host);
110+
int defaultPort = "https".equals(uri.getScheme()) ? 443 : 80;
111+
int port = (uri.getPort() != -1 ? uri.getPort() : defaultPort) + portOffset;
112+
host = uri.getScheme() + "://" + uri.getHost() + ":" + port;
113+
} catch (URISyntaxException e) {
114+
throw new RuntimeException(e);
115+
}
116+
}
117+
return host;
118+
}
119+
120+
private HttpClient createHttpClient() {
121+
return HttpClient.newBuilder()
122+
.followRedirects(HttpClient.Redirect.ALWAYS)
123+
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
124+
.version(HttpClient.Version.HTTP_1_1)
125+
.build();
126+
}
127+
128+
private HttpResponse<String> submitCustomerForm(HttpClient client, String name) throws IOException, InterruptedException, URISyntaxException {
129+
String serverHost = getServerHost(0);
130+
URI formUri = new URI(serverHost + APP_CONTEXT + "/addCustomer.jsf");
131+
132+
HttpResponse<String> getResponse = client.send(
133+
HttpRequest.newBuilder(formUri).GET().build(),
134+
HttpResponse.BodyHandlers.ofString());
135+
assertEquals(200, getResponse.statusCode());
136+
String body = getResponse.body();
137+
// JSF requires the ViewState token from the hidden form field to accept a POST submission
138+
Matcher viewStateMatcher = Pattern.compile("name=\"jakarta\\.faces\\.ViewState\"[^>]*value=\"([^\"]+)\"").matcher(body);
139+
assertTrue("ViewState not found in response", viewStateMatcher.find());
140+
String viewState = viewStateMatcher.group(1);
141+
142+
HttpRequest postRequest = HttpRequest.newBuilder(formUri)
143+
.header("Content-Type", "application/x-www-form-urlencoded")
144+
.POST(ofFormData(Map.of(
145+
"addCustomerForm", "addCustomerForm",
146+
"addCustomerForm:name", name,
147+
"addCustomerForm:add", "Add",
148+
"jakarta.faces.ViewState", viewState)))
149+
.build();
150+
return client.send(postRequest, HttpResponse.BodyHandlers.ofString());
151+
}
152+
153+
private void waitForMessagesReceived(long expected, long timeoutMs) throws IOException, InterruptedException {
154+
long deadline = System.currentTimeMillis() + timeoutMs;
155+
while (System.currentTimeMillis() < deadline) {
156+
if (getMessagesReceived() == expected) return;
157+
Thread.sleep(500);
158+
}
159+
assertEquals("Timed out waiting for messages received to reach " + expected,
160+
expected, getMessagesReceived());
161+
}
162+
163+
private void assertMessagesStayAt(long expected, long durationMs) throws IOException, InterruptedException {
164+
long deadline = System.currentTimeMillis() + durationMs;
165+
while (System.currentTimeMillis() < deadline) {
166+
assertEquals(expected, getMessagesReceived());
167+
Thread.sleep(500);
168+
}
169+
}
170+
171+
private long getMessagesReceived() throws IOException, InterruptedException {
172+
String server2Host = getServerHost(100);
173+
HttpClient client = HttpClient.newBuilder()
174+
.connectTimeout(Duration.ofMinutes(1))
175+
.build();
176+
HttpRequest request = HttpRequest.newBuilder()
177+
.uri(URI.create(server2Host + "/jts-application-component-2/messages/count"))
178+
.GET()
179+
.build();
180+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
181+
assertEquals("Message count request failed with status " + response.statusCode(),
182+
200, response.statusCode());
183+
return Long.parseLong(response.body().trim());
184+
}
185+
186+
private static BodyPublisher ofFormData(Map<String, String> data) {
187+
StringBuilder builder = new StringBuilder();
188+
for (Map.Entry<String, String> entry : data.entrySet()) {
189+
if (builder.length() > 0) {
190+
builder.append("&");
191+
}
192+
builder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
193+
builder.append("=");
194+
builder.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
195+
}
196+
return BodyPublishers.ofString(builder.toString());
197+
}
198+
}

0 commit comments

Comments
 (0)