Skip to content

Commit 7257fe7

Browse files
Replace rest-assured with Java HttpClient to fix Java 25 PCT failures (#793)
* BEE-72087: Replace rest-assured with Java HttpClient to fix Java 25 PCT failures rest-assured:5.3.2 transitively pulls in groovy:4.0.11 which introduces asmResolving=true as the default class resolution strategy. When tests run on Java 25, GroovyClassLoader compiles WorkflowScript at major version 69 at runtime. ASM 9.5 (in groovy:4.0.11) supports up to version 64, causing: IllegalArgumentException: Unsupported class file major version 69 rest-assured was only used in GitHubWebHookFullTest for plain HTTP request/response assertions. Replace it with java.net.http.HttpClient (built-in since Java 11) to eliminate the transitive groovy:4.0.11 dependency entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Test with Java 25 By analogy to #368, bump CI configurations to test on Java 25 (Linux) and Java 21 (Windows) to prove Java 25 compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fixing indentation * removoing depricated apis * adding close for HttpClinet * reformating code * httpClient instanceof AutoCloseable closeable casting to object * Restore comment explaining why APPLICATION_JSON has no charset suffix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * empty commit to re trigger build --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8e3b016 commit 7257fe7

3 files changed

Lines changed: 90 additions & 104 deletions

File tree

Jenkinsfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
buildPlugin(useContainerAgent: true, configurations: [
2-
[platform: 'linux', jdk: 21],
3-
[platform: 'windows', jdk: 17],
2+
[platform: 'linux', jdk: 25],
3+
[platform: 'windows', jdk: 21],
44
])

pom.xml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,6 @@
128128

129129
<!--TEST DEPS-->
130130

131-
<!-- 4.5.3 used by rest-assured -->
132-
<dependency>
133-
<groupId>org.jenkins-ci.plugins</groupId>
134-
<artifactId>apache-httpcomponents-client-4-api</artifactId>
135-
<scope>test</scope>
136-
</dependency>
137-
138131
<dependency>
139132
<groupId>org.mockito</groupId>
140133
<artifactId>mockito-core</artifactId>
@@ -190,12 +183,6 @@
190183
<scope>test</scope>
191184
</dependency>
192185

193-
<dependency>
194-
<groupId>io.rest-assured</groupId>
195-
<artifactId>rest-assured</artifactId>
196-
<version>5.3.2</version>
197-
<scope>test</scope>
198-
</dependency>
199186

200187
</dependencies>
201188

Lines changed: 88 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package com.cloudbees.jenkins;
22

33
import com.google.common.base.Charsets;
4-
import com.google.common.net.HttpHeaders;
5-
import io.restassured.builder.RequestSpecBuilder;
6-
import io.restassured.http.Header;
7-
import io.restassured.specification.RequestSpecification;
84
import jakarta.inject.Inject;
95
import org.apache.commons.io.IOUtils;
106
import org.jenkinsci.plugins.github.config.GitHubPluginConfig;
117
import org.jenkinsci.plugins.github.webhook.GHEventHeader;
128
import org.jenkinsci.plugins.github.webhook.GHEventPayload;
9+
import org.junit.jupiter.api.AfterEach;
1310
import org.junit.jupiter.api.BeforeEach;
1411
import org.junit.jupiter.api.Test;
1512
import org.jvnet.hudson.test.JenkinsRule;
@@ -18,20 +15,28 @@
1815

1916
import java.io.File;
2017
import java.io.IOException;
18+
import java.net.URI;
19+
import java.util.Locale;
20+
import java.net.URLEncoder;
21+
import java.net.http.HttpClient;
22+
import java.net.http.HttpRequest;
23+
import java.net.http.HttpResponse;
24+
import java.nio.charset.StandardCharsets;
2125

22-
import static io.restassured.RestAssured.given;
23-
import static io.restassured.config.EncoderConfig.encoderConfig;
24-
import static io.restassured.config.RestAssuredConfig.newConfig;
2526
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
2627
import static jakarta.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
2728
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
2829
import static java.lang.String.format;
2930
import static org.apache.commons.lang3.ClassUtils.PACKAGE_SEPARATOR;
31+
import static org.hamcrest.MatcherAssert.assertThat;
3032
import static org.hamcrest.Matchers.containsString;
33+
import static org.hamcrest.Matchers.is;
3134
import static org.hamcrest.Matchers.notNullValue;
3235
import static org.jenkinsci.plugins.github.test.HookSecretHelper.removeSecretIn;
3336
import static org.jenkinsci.plugins.github.test.HookSecretHelper.storeSecretIn;
34-
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.*;
37+
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SHA256_PREFIX;
38+
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SIGNATURE_HEADER;
39+
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SIGNATURE_HEADER_SHA256;
3540

3641
/**
3742
* @author lanwen (Merkushev Kirill)
@@ -43,133 +48,131 @@ public class GitHubWebHookFullTest {
4348
public static final String APPLICATION_JSON = GHEventPayload.PayloadHandler.APPLICATION_JSON;
4449
public static final String FORM = GHEventPayload.PayloadHandler.FORM_URLENCODED;
4550

46-
public static final Header JSON_CONTENT_TYPE = new Header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
47-
public static final Header FORM_CONTENT_TYPE = new Header(HttpHeaders.CONTENT_TYPE, FORM);
48-
public static final String NOT_NULL_VALUE = "nonnull";
49-
50-
private RequestSpecification spec;
51-
5251
@Inject
5352
private GitHubPluginConfig config;
5453

5554
private JenkinsRule jenkins;
55+
private HttpClient httpClient;
5656

5757
@BeforeEach
5858
void before(JenkinsRule rule) throws Throwable {
5959
jenkins = rule;
6060
jenkins.getInstance().getInjector().injectMembers(this);
61+
httpClient = HttpClient.newHttpClient();
62+
}
6163

62-
spec = new RequestSpecBuilder()
63-
.setConfig(newConfig()
64-
.encoderConfig(encoderConfig()
65-
.defaultContentCharset(Charsets.UTF_8.name())
66-
// GitHub doesn't add charsets, so don't test with them
67-
.appendDefaultContentCharsetToContentTypeIfUndefined(false)))
68-
.build();
64+
@AfterEach
65+
void after() throws Exception {
66+
if ((Object) httpClient instanceof AutoCloseable closeable) { // TODO: replace with httpClient.close() once jenkins.baseline is Java 21+
67+
closeable.close();
68+
}
6969
}
7070

7171
@Test
7272
void shouldParseJsonWebHookFromGH() throws Exception {
7373
removeSecretIn(config);
74-
given().spec(spec)
75-
.header(eventHeader(GHEvent.PUSH))
76-
.header(JSON_CONTENT_TYPE)
77-
.body(classpath("payloads/push.json"))
78-
.log().all()
79-
.expect().log().all().statusCode(SC_OK).request().post(getPath());
74+
HttpResponse<String> response = httpClient.send(
75+
HttpRequest.newBuilder(URI.create(getPath()))
76+
.POST(HttpRequest.BodyPublishers.ofString(classpath("payloads/push.json")))
77+
.header("Content-Type", APPLICATION_JSON)
78+
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase(Locale.ROOT))
79+
.build(),
80+
HttpResponse.BodyHandlers.ofString());
81+
assertThat("status", response.statusCode(), is(SC_OK));
8082
}
8183

82-
8384
@Test
8485
void shouldParseJsonWebHookFromGHWithSignHeader() throws Exception {
8586
String hash = "355e155fc3d10c4e5f2c6086a01281d2e947d932";
8687
String hash256 = "85e61999573c7023720a12375e1e55d18a0870e1ef880736f6ffc9273d0519e3";
8788
String secret = "123";
8889

8990
storeSecretIn(config, secret);
90-
given().spec(spec)
91-
.header(eventHeader(GHEvent.PUSH))
92-
.header(JSON_CONTENT_TYPE)
93-
.header(SIGNATURE_HEADER, format("sha1=%s", hash))
94-
.header(SIGNATURE_HEADER_SHA256, format("%s%s", SHA256_PREFIX, hash256))
95-
.body(classpath(String.format("payloads/ping_hash_%s_secret_%s.json", hash, secret)))
96-
.log().all()
97-
.expect().log().all().statusCode(SC_OK).request().post(getPath());
91+
HttpResponse<String> response = httpClient.send(
92+
HttpRequest.newBuilder(URI.create(getPath()))
93+
.POST(HttpRequest.BodyPublishers.ofString(
94+
classpath(format("payloads/ping_hash_%s_secret_%s.json", hash, secret))))
95+
.header("Content-Type", APPLICATION_JSON)
96+
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase(Locale.ROOT))
97+
.header(SIGNATURE_HEADER, format("sha1=%s", hash))
98+
.header(SIGNATURE_HEADER_SHA256, format("%s%s", SHA256_PREFIX, hash256))
99+
.build(),
100+
HttpResponse.BodyHandlers.ofString());
101+
assertThat("status", response.statusCode(), is(SC_OK));
98102
}
99103

100104
@Test
101105
void shouldParseFormWebHookOrServiceHookFromGH() throws Exception {
102-
given().spec(spec)
103-
.header(eventHeader(GHEvent.PUSH))
104-
.header(FORM_CONTENT_TYPE)
105-
.formParam("payload", classpath("payloads/push.json"))
106-
.log().all()
107-
.expect().log().all().statusCode(SC_OK).request().post(getPath());
106+
String encoded = "payload=" + URLEncoder.encode(classpath("payloads/push.json"), StandardCharsets.UTF_8);
107+
HttpResponse<String> response = httpClient.send(
108+
HttpRequest.newBuilder(URI.create(getPath()))
109+
.POST(HttpRequest.BodyPublishers.ofString(encoded))
110+
.header("Content-Type", FORM)
111+
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase(Locale.ROOT))
112+
.build(),
113+
HttpResponse.BodyHandlers.ofString());
114+
assertThat("status", response.statusCode(), is(SC_OK));
108115
}
109116

110117
@Test
111118
void shouldParsePingFromGH() throws Exception {
112-
given().spec(spec)
113-
.header(eventHeader(GHEvent.PING))
114-
.header(JSON_CONTENT_TYPE)
115-
.body(classpath("payloads/ping.json"))
116-
.log().all()
117-
.expect().log().all()
118-
.statusCode(SC_OK)
119-
.request()
120-
.post(getPath());
119+
HttpResponse<String> response = httpClient.send(
120+
HttpRequest.newBuilder(URI.create(getPath()))
121+
.POST(HttpRequest.BodyPublishers.ofString(classpath("payloads/ping.json")))
122+
.header("Content-Type", APPLICATION_JSON)
123+
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PING.name().toLowerCase(Locale.ROOT))
124+
.build(),
125+
HttpResponse.BodyHandlers.ofString());
126+
assertThat("status", response.statusCode(), is(SC_OK));
121127
}
122128

123129
@Test
124130
void shouldReturnErrOnEmptyPayloadAndHeader() throws Exception {
125-
given().spec(spec)
126-
.log().all()
127-
.expect().log().all()
128-
.statusCode(SC_BAD_REQUEST)
129-
.body(containsString("Hook should contain event type"))
130-
.request()
131-
.post(getPath());
131+
HttpResponse<String> response = httpClient.send(
132+
HttpRequest.newBuilder(URI.create(getPath()))
133+
.POST(HttpRequest.BodyPublishers.noBody())
134+
.build(),
135+
HttpResponse.BodyHandlers.ofString());
136+
assertThat("status", response.statusCode(), is(SC_BAD_REQUEST));
137+
assertThat("body", response.body(), containsString("Hook should contain event type"));
132138
}
133139

134140
@Test
135141
void shouldReturnErrOnEmptyPayload() throws Exception {
136-
given().spec(spec)
137-
.header(eventHeader(GHEvent.PUSH))
138-
.log().all()
139-
.expect().log().all()
140-
.statusCode(SC_BAD_REQUEST)
141-
.body(containsString("Hook should contain payload"))
142-
.request()
143-
.post(getPath());
142+
HttpResponse<String> response = httpClient.send(
143+
HttpRequest.newBuilder(URI.create(getPath()))
144+
.POST(HttpRequest.BodyPublishers.noBody())
145+
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase(Locale.ROOT))
146+
.build(),
147+
HttpResponse.BodyHandlers.ofString());
148+
assertThat("status", response.statusCode(), is(SC_BAD_REQUEST));
149+
assertThat("body", response.body(), containsString("Hook should contain payload"));
144150
}
145151

146152
@Test
147153
void shouldReturnErrOnGetReq() throws Exception {
148-
given().spec(spec)
149-
.log().all().expect().log().all()
150-
.statusCode(SC_METHOD_NOT_ALLOWED)
151-
.request()
152-
.get(getPath());
154+
HttpResponse<String> response = httpClient.send(
155+
HttpRequest.newBuilder(URI.create(getPath()))
156+
.GET()
157+
.build(),
158+
HttpResponse.BodyHandlers.ofString());
159+
assertThat("status", response.statusCode(), is(SC_METHOD_NOT_ALLOWED));
153160
}
154161

155162
@Test
156163
void shouldProcessSelfTest() throws Exception {
157-
given().spec(spec)
158-
.header(new Header(GitHubWebHook.URL_VALIDATION_HEADER, NOT_NULL_VALUE))
159-
.log().all()
160-
.expect().log().all()
161-
.statusCode(SC_OK)
162-
.header(GitHubWebHook.X_INSTANCE_IDENTITY, notNullValue())
163-
.request()
164-
.post(getPath());
164+
HttpResponse<String> response = httpClient.send(
165+
HttpRequest.newBuilder(URI.create(getPath()))
166+
.POST(HttpRequest.BodyPublishers.noBody())
167+
.header(GitHubWebHook.URL_VALIDATION_HEADER, "nonnull")
168+
.build(),
169+
HttpResponse.BodyHandlers.ofString());
170+
assertThat("status", response.statusCode(), is(SC_OK));
171+
assertThat("identity header", response.headers().firstValue(GitHubWebHook.X_INSTANCE_IDENTITY).orElse(null), notNullValue());
165172
}
166173

167-
public Header eventHeader(GHEvent event) {
168-
return eventHeader(event.name().toLowerCase());
169-
}
170-
171-
public Header eventHeader(String event) {
172-
return new Header(GHEventHeader.PayloadHandler.EVENT_HEADER, event);
174+
private String getPath() {
175+
return jenkins.getInstance().getRootUrl() + GitHubWebHook.URLNAME.concat("/");
173176
}
174177

175178
public static String classpath(String path) {
@@ -185,8 +188,4 @@ public static String classpath(Class<?> clazz, String path) {
185188
throw new RuntimeException(format("Can't load %s for class %s", path, clazz), e);
186189
}
187190
}
188-
189-
private String getPath(){
190-
return jenkins.getInstance().getRootUrl() + GitHubWebHook.URLNAME.concat("/");
191-
}
192191
}

0 commit comments

Comments
 (0)