Skip to content

Commit ace27c6

Browse files
committed
✨ Added support files for using testing against an AEM instance.
1 parent adadcd0 commit ace27c6

3 files changed

Lines changed: 238 additions & 0 deletions

File tree

spring/fluentforms-sample-webmvc-app/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@
9898
<artifactId>htmlunit</artifactId>
9999
<scope>test</scope>
100100
</dependency>
101+
<dependency>
102+
<groupId>org.testcontainers</groupId>
103+
<artifactId>junit-jupiter</artifactId>
104+
<scope>test</scope>
105+
</dependency>
106+
<dependency>
107+
<groupId>org.awaitility</groupId>
108+
<artifactId>awaitility</artifactId>
109+
<scope>test</scope>
110+
</dependency>
101111
</dependencies>
102112

103113
<build>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com._4point.aem.fluentforms.sampleapp.resources;
2+
3+
import java.io.IOException;
4+
import java.net.URI;
5+
import java.net.http.HttpClient;
6+
import java.net.http.HttpRequest;
7+
import java.net.http.HttpResponse;
8+
import java.net.http.HttpResponse.BodyHandlers;
9+
import java.nio.file.Path;
10+
import java.util.Base64;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.atomic.AtomicBoolean;
13+
14+
import org.awaitility.Awaitility;
15+
import org.testcontainers.containers.GenericContainer;
16+
import org.testcontainers.utility.DockerImageName;
17+
18+
/**
19+
* This singleton defines the AEM instance that is being used for the integration tests.
20+
*
21+
* Change the value of TestConstants.AEM_TARGET_TYPE in the TestConstants class to tell the tests what type of AEM instance we're testing against.
22+
*/
23+
public enum AemInstance {
24+
AEM_1(TestConstants.AEM_TARGET_TYPE); // Change parameter to false to disable TestContainers and use local AEM instance..,
25+
26+
private static final String LOCALHOST = "localhost";
27+
28+
29+
private final AemTargetType targetType;
30+
private final GenericContainer<?> aemContainer;
31+
private final AtomicBoolean preparedForTests = new AtomicBoolean(false);
32+
33+
@SuppressWarnings("resource")
34+
private AemInstance(AemTargetType targetType) {
35+
this(targetType, targetType == AemTargetType.TESTCONTAINERS ? new GenericContainer<>(DockerImageName.parse(TestConstants.AEM_IMAGE_NAME))
36+
.withReuse(true)
37+
.withExposedPorts(4502)
38+
: null);
39+
}
40+
41+
private AemInstance(AemTargetType targetType, GenericContainer<?> aemContainer) {
42+
this.targetType = targetType;
43+
this.aemContainer = aemContainer;
44+
if (aemContainer != null) {
45+
aemContainer.start();
46+
}
47+
}
48+
49+
public void prepareForTests() {
50+
if (!preparedForTests.get()) {
51+
Integer mappedPort = aemPort();
52+
53+
startAem(mappedPort);
54+
// deploySampleFiles(mappedPort);
55+
56+
preparedForTests.set(true);
57+
System.out.println("Starting tests...");
58+
}
59+
}
60+
61+
// Make sure AEM is up before running the tests.
62+
private static void startAem(Integer mappedPort) {
63+
System.out.println(String.format("Checking if AEM is available on port %d.", mappedPort));
64+
HttpClient client = HttpClient.newHttpClient();
65+
HttpRequest request = HttpRequest.newBuilder()
66+
.uri(URI.create(String.format("http://localhost:%d/", mappedPort)))
67+
.header("Authorization", encodeBasic(TestConstants.TEST_USER, TestConstants.TEST_USER_PASSWORD))
68+
.GET()
69+
.build();
70+
71+
if (!aemIsUp(client, request)) {
72+
System.out.println(String.format("Waiting for AEM to become available."));
73+
Awaitility.await()
74+
.atMost(5, TimeUnit.MINUTES)
75+
.pollDelay(50, TimeUnit.SECONDS)
76+
.pollInterval(10, TimeUnit.SECONDS)
77+
.until(()->aemIsUp(client, request));
78+
}
79+
System.out.println("AEM is available.");
80+
}
81+
82+
// public static void deploySampleFiles(Integer mappedPort) {
83+
// System.out.println("Deploying sample files...");
84+
//
85+
// SamplesDeployer deployer = new SamplesDeployer(LOCALHOST, mappedPort, TestConstants.TEST_USER, TestConstants.TEST_USER_PASSWORD);
86+
//
87+
// deployer.deployXdp(Paths.get("src", "test", "resources", "SampleForm.xdp"), "sample-forms");
88+
// deployer.deployAf(Paths.get("src", "test", "resources", "sample00002test.zip"));
89+
// // TODO: Create a deploy a sample AF that uses a JSON schema
90+
//// deployer.deployAf(Paths.get("src", "test", "resources", "SampleForm.xdp"), "sample-forms");
91+
// System.out.println("Sample files deployed.");
92+
// }
93+
94+
/**
95+
* Host name where AEM is running
96+
*
97+
* @return
98+
*/
99+
public String aemHost() {
100+
return switch (targetType) {
101+
case LOCAL, TESTCONTAINERS -> LOCALHOST;
102+
case REMOTE_WINDOWS, REMOTE_LINUX -> TestConstants.TEST_MACHINE_NAME;
103+
};
104+
}
105+
106+
/**
107+
* Port where AEM is running.
108+
*
109+
* @return
110+
*/
111+
public Integer aemPort() {
112+
return switch (targetType) {
113+
case LOCAL, REMOTE_WINDOWS, REMOTE_LINUX -> Integer.parseInt(TestConstants.TEST_MACHINE_PORT_STR);
114+
case TESTCONTAINERS -> aemContainer.getMappedPort(4502);
115+
};
116+
}
117+
118+
public Boolean isLinux() {
119+
return switch (targetType) {
120+
case LOCAL -> !System.getProperty("os.name").toLowerCase().contains("windows"); // Treat MAC as Linux
121+
case REMOTE_WINDOWS -> false;
122+
case REMOTE_LINUX, TESTCONTAINERS -> true;
123+
};
124+
}
125+
/**
126+
* Is AEM running locally or remotely?
127+
*
128+
* If is is running in a container, then it is considered remote (because it does not have access to the local file system).
129+
* If AEM is running locally, then local file paths can be used, otherwise remote file paths will need to be used for some tests.
130+
*
131+
* @return
132+
*/
133+
public boolean isLocal() {
134+
return aemContainer == null && aemHost().equalsIgnoreCase(LOCALHOST);
135+
}
136+
137+
private static boolean aemIsUp(HttpClient restClient, HttpRequest request) {
138+
try {
139+
HttpResponse<Void> result = restClient.send(request, BodyHandlers.discarding());
140+
int statusCode = result.statusCode();
141+
System.out.println(String.format("AEM returned status code %d.", statusCode));
142+
return statusCode >= 200 && statusCode <= 399; // Wait for successful status code.
143+
} catch (InterruptedException e) {
144+
return false;
145+
} catch (IOException e) {
146+
e.printStackTrace();
147+
return false;
148+
}
149+
}
150+
151+
private static String encodeBasic(String username, String password) {
152+
return "Basic "+ Base64
153+
.getEncoder()
154+
.encodeToString((username+":"+password).getBytes());
155+
}
156+
157+
/**
158+
* Enum representing the different target types for AEM instances in the
159+
* integration tests. Each enum value corresponds to a specific environment
160+
* where AEM is expected to run, along with the path to the sample files used in
161+
* the tests.
162+
*/
163+
public enum AemTargetType {
164+
LOCAL(Path.of("..", "test_containers", "ff_it_files").toAbsolutePath()), // Running on local machine (assumes that the port is TEST_MACHINE_PORT)
165+
REMOTE_WINDOWS(Path.of("/Adobe", "ff_it_files")), // Running on remote Windows machine (assumes that machine name is TEST_MACHINE_NAME and port is TEST_MACHINE_PORT)
166+
REMOTE_LINUX(Path.of("/opt", "adobe", "ff_it_files")), // Running on remote Linux machine (assumes that machine name is TEST_MACHINE_NAME and port is TEST_MACHINE_PORT)
167+
TESTCONTAINERS(Path.of("/opt", "adobe", "ff_it_files")); // Running on local testcontainers image (gets port from TestContainers)
168+
169+
private final Path samplesPath; // Location where sample files are stored for this AEM target type.
170+
// Needs to be absolute path since (in some cases) it will be passed to AEM.
171+
172+
private AemTargetType(Path samplesPath) {
173+
this.samplesPath = samplesPath;
174+
}
175+
176+
/**
177+
* Returns the path to the samples directory for this AEM target type.
178+
*
179+
* @return Path to the samples directory.
180+
*/
181+
public Path samplesPath(String filename) {
182+
return this.samplesPath.resolve(filename);
183+
}
184+
}
185+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com._4point.aem.fluentforms.sampleapp.resources;
2+
3+
import java.util.EnumSet;
4+
import java.util.Set;
5+
6+
import com._4point.aem.fluentforms.sampleapp.resources.AemInstance.AemTargetType;
7+
8+
public class TestConstants {
9+
10+
public static final String TEST_MACHINE_NAME = "localhost"; //"172.22.132.85";
11+
// public static final AemServerType TEST_MACHINE_AEM_TYPE = AemServerType.StandardType.OSGI;
12+
public static final int TEST_MACHINE_PORT = 4502;
13+
public static final String TEST_MACHINE_PORT_STR = Integer.toString(TEST_MACHINE_PORT);
14+
public static final String TEST_USER = "admin";
15+
public static final String TEST_USER_PASSWORD = "admin";
16+
17+
// The TESTCONTAINERS configuration requires an AEM container image with AEM forms installed. Since AEM is proprietary, it is not possible to obtain this
18+
// through public images. By default, this uses a private image hosted in the 4PointSolutions-PS GitHub organization. If you are not
19+
// part of that prg, you will have to supply your own image.
20+
// public static final String AEM_IMAGE_NAME = "ghcr.io/4pointsolutions-ps/aem:aem65sp21";
21+
public static final String AEM_IMAGE_NAME = "aem_lts_it_tests:aem65ltssp1_it_tests";
22+
23+
// The following Set controls whether the integration tests are run with WireMock, TestContainers or both.
24+
// This is normally set to just WIREMOCK since that is what is required for the GitHub actions CI pipeline,
25+
// but it may be set to other values when testing locally.
26+
// private static Set<IntegrationTestType> TESTS_TO_RUN = EnumSet.of(IntegrationTestType.WIREMOCK);
27+
28+
// If the local environment has docker installed and has a local AEM container image available, then the
29+
// AEM_IMAGE_NAME can be modified, and the following line uncommented to run both WireMock and an AEM instance
30+
// tests.
31+
private static Set<IntegrationTestType> TESTS_TO_RUN = EnumSet.of(IntegrationTestType.WIREMOCK, IntegrationTestType.AEM_INSTANCE);
32+
33+
34+
private enum IntegrationTestType { WIREMOCK, AEM_INSTANCE };
35+
36+
public static boolean runWiremockTests() { return TESTS_TO_RUN.contains(IntegrationTestType.WIREMOCK); }
37+
public static boolean runTestContainerTests() { return TESTS_TO_RUN.contains(IntegrationTestType.AEM_INSTANCE); }
38+
39+
// Set this to indicate the type of machine that AEM is running on:
40+
// The integration tests will run against an AEM instance running in a Docker container otherwise they will run against a local AEM instance.
41+
public static final AemTargetType AEM_TARGET_TYPE = AemTargetType.TESTCONTAINERS;
42+
43+
}

0 commit comments

Comments
 (0)