Skip to content

Commit 152f442

Browse files
committed
Webhook test and Testcontainers networking
1 parent 9dff6c2 commit 152f442

File tree

4 files changed

+235
-0
lines changed

4 files changed

+235
-0
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<testcontainers.version>1.18.1</testcontainers.version>
2323
<junit.version>5.9.3</junit.version>
2424
<assertj.version>3.24.2</assertj.version>
25+
<awaitility.version>4.2.0</awaitility.version>
2526
<project.scm.id>github</project.scm.id>
2627
</properties>
2728

@@ -87,6 +88,12 @@
8788
<artifactId>assertj-core</artifactId>
8889
<scope>test</scope>
8990
</dependency>
91+
<dependency>
92+
<groupId>org.awaitility</groupId>
93+
<artifactId>awaitility</artifactId>
94+
<version>${awaitility.version}</version>
95+
<scope>test</scope>
96+
</dependency>
9097
<dependency>
9198
<groupId>ch.qos.logback</groupId>
9299
<artifactId>logback-classic</artifactId>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (C) 2023 WireMock Inc, Oleg Nenashev and all project contributors
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.wiremock.integrations.testcontainers;
17+
18+
import org.junit.Rule;
19+
import org.junit.Test;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
import org.testcontainers.Testcontainers;
23+
import org.testcontainers.containers.GenericContainer;
24+
import org.testcontainers.containers.output.Slf4jLogConsumer;
25+
import org.wiremock.integrations.testcontainers.testsupport.http.TestHttpClient;
26+
import org.wiremock.integrations.testcontainers.testsupport.http.TestHttpServer;
27+
28+
import java.net.http.HttpResponse;
29+
import java.nio.file.Paths;
30+
import java.time.Duration;
31+
import java.util.Collections;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
35+
36+
/**
37+
* Tests the WireMock Webhook extension and TestContainers Networking
38+
* For this type of tests we should use following steps:
39+
* <p>
40+
* Use {@link GenericContainer#withAccessToHost(boolean)} to force the host access mechanism
41+
* <p>
42+
* Use {@link Testcontainers#exposeHostPorts(int...)} to expose host machine ports to containers
43+
* <p>
44+
* Use {@link GenericContainer#INTERNAL_HOST_HOSTNAME} to calculate hostname for callback
45+
*
46+
* @see <a href="https://www.testcontainers.org/features/networking/">Testcontainers Networking</a>
47+
*/
48+
public class WireMockContainerExtensionsWebhookTest {
49+
50+
public static final Logger LOGGER = LoggerFactory.getLogger(WireMockContainerExtensionsWebhookTest.class);
51+
public static final String WIREMOCK_PATH = "/wiremock/callback-trigger";
52+
public static final String APPLICATION_PATH = "/application/callback-receiver";
53+
54+
55+
public TestHttpServer applicationServer = TestHttpServer.newInstance();
56+
@Rule
57+
public WireMockContainer wiremockServer = new WireMockContainer("2.35.0")
58+
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
59+
.withCliArg("--global-response-templating")
60+
.withMapping("webhook-callback-template", WireMockContainerExtensionsWebhookTest.class, "webhook-callback-template.json")
61+
.withExtension("Webhook",
62+
Collections.singleton("org.wiremock.webhooks.Webhooks"),
63+
Collections.singleton(Paths.get("target", "test-wiremock-extension", "wiremock-webhooks-extension-2.35.0.jar").toFile()))
64+
.withAccessToHost(true); // Force the host access mechanism
65+
66+
67+
@Test
68+
public void callbackUsingJsonStub() throws Exception {
69+
// given
70+
Testcontainers.exposeHostPorts(applicationServer.getPort()); // Exposing host ports to the container
71+
72+
String wiremockUrl = wiremockServer.getUrl(WIREMOCK_PATH);
73+
String applicationCallbackUrl = String.format("http://%s:%d%s", GenericContainer.INTERNAL_HOST_HOSTNAME, applicationServer.getPort(), APPLICATION_PATH);
74+
75+
// when
76+
HttpResponse<String> response = TestHttpClient.newInstance().post(
77+
wiremockUrl,
78+
"{\"callbackMethod\": \"PUT\", \"callbackUrl\": \"" + applicationCallbackUrl + "\"}"
79+
);
80+
81+
// then
82+
assertThat(response).as("Wiremock Response").isNotNull().satisfies(it -> {
83+
assertThat(it.statusCode()).as("Wiremock Response Status").isEqualTo(200);
84+
assertThat(it.body()).as("Wiremock Response Body")
85+
.contains("Please wait callback")
86+
.contains("PUT")
87+
.contains(applicationCallbackUrl);
88+
});
89+
90+
await().atMost(Duration.ofMillis(5000)).untilAsserted(() -> {
91+
assertThat(applicationServer.getRecordedRequests()).as("Received Callback")
92+
.hasSize(1)
93+
.first().usingRecursiveComparison()
94+
.isEqualTo(new TestHttpServer.RecordedRequest("PUT", APPLICATION_PATH, "Async processing Finished"));
95+
});
96+
}
97+
98+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.wiremock.integrations.testcontainers.testsupport.http;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpHandler;
5+
import com.sun.net.httpserver.HttpServer;
6+
7+
import java.io.IOException;
8+
import java.net.InetSocketAddress;
9+
import java.nio.charset.StandardCharsets;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public class TestHttpServer {
14+
private final HttpServer server;
15+
private final AllRequestsRecorder handler;
16+
17+
public static TestHttpServer newInstance() {
18+
try {
19+
return new TestHttpServer(0);
20+
} catch (IOException e) {
21+
throw new RuntimeException("Failed to start Test Http Server", e);
22+
}
23+
}
24+
25+
private TestHttpServer(int port) throws IOException {
26+
// handlers
27+
handler = new AllRequestsRecorder();
28+
// server
29+
server = HttpServer.create(new InetSocketAddress(port), 0);
30+
server.createContext("/", handler);
31+
server.start();
32+
}
33+
34+
public int getPort() {
35+
return server.getAddress().getPort();
36+
}
37+
38+
public List<RecordedRequest> getRecordedRequests() {
39+
return handler.getRecordedRequests();
40+
}
41+
42+
43+
private static final class AllRequestsRecorder implements HttpHandler {
44+
45+
private final List<RecordedRequest> recordedRequests = new ArrayList<>();
46+
47+
@Override
48+
public void handle(HttpExchange exchange) throws IOException {
49+
String method = exchange.getRequestMethod();
50+
String path = exchange.getRequestURI().getPath();
51+
String body = exchange.getRequestBody().available() > 0
52+
? new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8)
53+
: null;
54+
55+
recordedRequests.add(new RecordedRequest(method, path, body));
56+
57+
exchange.sendResponseHeaders(200, 0);
58+
exchange.getResponseBody().close();
59+
}
60+
61+
public List<RecordedRequest> getRecordedRequests() {
62+
return recordedRequests;
63+
}
64+
}
65+
66+
public static final class RecordedRequest {
67+
private final String method;
68+
private final String path;
69+
private final String body;
70+
71+
public RecordedRequest(String method, String path, String body) {
72+
this.method = method;
73+
this.path = path;
74+
this.body = body;
75+
}
76+
77+
public String getMethod() {
78+
return method;
79+
}
80+
81+
public String getPath() {
82+
return path;
83+
}
84+
85+
public String getBody() {
86+
return body;
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return "RecordedRequest{" +
92+
"method='" + method + '\'' +
93+
", path='" + path + '\'' +
94+
", body='" + body + '\'' +
95+
'}';
96+
}
97+
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"request": {
3+
"method": "POST",
4+
"urlPath": "/wiremock/callback-trigger"
5+
},
6+
"response": {
7+
"status": 200,
8+
"headers": {
9+
"Content-Type": "application/json"
10+
},
11+
"jsonBody": {
12+
"message": "Please wait callback",
13+
"method": "{{jsonPath request.body '$.callbackMethod'}}",
14+
"url": "{{jsonPath request.body '$.callbackUrl'}}"
15+
}
16+
},
17+
"postServeActions": [
18+
{
19+
"name": "webhook",
20+
"parameters": {
21+
"method": "{{jsonPath originalRequest.body '$.callbackMethod'}}",
22+
"url": "{{jsonPath originalRequest.body '$.callbackUrl'}}",
23+
"body": "Async processing Finished",
24+
"delay": {
25+
"type": "fixed",
26+
"milliseconds": 1000
27+
}
28+
}
29+
}
30+
]
31+
}

0 commit comments

Comments
 (0)