Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
buildPlugin(useContainerAgent: true, configurations: [
[platform: 'linux', jdk: 21],
[platform: 'windows', jdk: 17],
[platform: 'linux', jdk: 25],
[platform: 'windows', jdk: 21],
])
13 changes: 0 additions & 13 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,6 @@

<!--TEST DEPS-->

<!-- 4.5.3 used by rest-assured -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>apache-httpcomponents-client-4-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down Expand Up @@ -190,12 +183,6 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
171 changes: 80 additions & 91 deletions src/test/java/com/cloudbees/jenkins/GitHubWebHookFullTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.cloudbees.jenkins;

import com.google.common.base.Charsets;
import com.google.common.net.HttpHeaders;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.http.Header;
import io.restassured.specification.RequestSpecification;
import jakarta.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.github.config.GitHubPluginConfig;
Expand All @@ -18,158 +14,155 @@

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

import static io.restassured.RestAssured.given;
import static io.restassured.config.EncoderConfig.encoderConfig;
import static io.restassured.config.RestAssuredConfig.newConfig;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.lang.String.format;
import static org.apache.commons.lang3.ClassUtils.PACKAGE_SEPARATOR;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.jenkinsci.plugins.github.test.HookSecretHelper.removeSecretIn;
import static org.jenkinsci.plugins.github.test.HookSecretHelper.storeSecretIn;
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.*;
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SHA256_PREFIX;
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SIGNATURE_HEADER;
import static org.jenkinsci.plugins.github.webhook.RequirePostWithGHHookPayload.Processor.SIGNATURE_HEADER_SHA256;

/**
* @author lanwen (Merkushev Kirill)
*/
@WithJenkins
public class GitHubWebHookFullTest {

// GitHub doesn't send the charset per docs, so re-use the exact content-type from the handler
Comment thread
nevingeorgesunny marked this conversation as resolved.
public static final String APPLICATION_JSON = GHEventPayload.PayloadHandler.APPLICATION_JSON;
public static final String FORM = GHEventPayload.PayloadHandler.FORM_URLENCODED;

public static final Header JSON_CONTENT_TYPE = new Header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
public static final Header FORM_CONTENT_TYPE = new Header(HttpHeaders.CONTENT_TYPE, FORM);
public static final String NOT_NULL_VALUE = "nonnull";

private RequestSpecification spec;

@Inject
private GitHubPluginConfig config;

private JenkinsRule jenkins;
private HttpClient httpClient;

@BeforeEach
void before(JenkinsRule rule) throws Throwable {
jenkins = rule;
jenkins.getInstance().getInjector().injectMembers(this);

spec = new RequestSpecBuilder()
.setConfig(newConfig()
.encoderConfig(encoderConfig()
.defaultContentCharset(Charsets.UTF_8.name())
// GitHub doesn't add charsets, so don't test with them
.appendDefaultContentCharsetToContentTypeIfUndefined(false)))
.build();
httpClient = HttpClient.newHttpClient();
Comment thread
nevingeorgesunny marked this conversation as resolved.
}

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


@Test
void shouldParseJsonWebHookFromGHWithSignHeader() throws Exception {
String hash = "355e155fc3d10c4e5f2c6086a01281d2e947d932";
String hash256 = "85e61999573c7023720a12375e1e55d18a0870e1ef880736f6ffc9273d0519e3";
String secret = "123";

storeSecretIn(config, secret);
given().spec(spec)
.header(eventHeader(GHEvent.PUSH))
.header(JSON_CONTENT_TYPE)
.header(SIGNATURE_HEADER, format("sha1=%s", hash))
.header(SIGNATURE_HEADER_SHA256, format("%s%s", SHA256_PREFIX, hash256))
.body(classpath(String.format("payloads/ping_hash_%s_secret_%s.json", hash, secret)))
.log().all()
.expect().log().all().statusCode(SC_OK).request().post(getPath());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.ofString(
classpath(format("payloads/ping_hash_%s_secret_%s.json", hash, secret))))
.header("Content-Type", APPLICATION_JSON)
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase())
.header(SIGNATURE_HEADER, format("sha1=%s", hash))
.header(SIGNATURE_HEADER_SHA256, format("%s%s", SHA256_PREFIX, hash256))
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_OK));
}

@Test
void shouldParseFormWebHookOrServiceHookFromGH() throws Exception {
given().spec(spec)
.header(eventHeader(GHEvent.PUSH))
.header(FORM_CONTENT_TYPE)
.formParam("payload", classpath("payloads/push.json"))
.log().all()
.expect().log().all().statusCode(SC_OK).request().post(getPath());
String encoded = "payload=" + URLEncoder.encode(classpath("payloads/push.json"), StandardCharsets.UTF_8);
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.ofString(encoded))
.header("Content-Type", FORM)
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase())
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_OK));
}

@Test
void shouldParsePingFromGH() throws Exception {
given().spec(spec)
.header(eventHeader(GHEvent.PING))
.header(JSON_CONTENT_TYPE)
.body(classpath("payloads/ping.json"))
.log().all()
.expect().log().all()
.statusCode(SC_OK)
.request()
.post(getPath());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.ofString(classpath("payloads/ping.json")))
.header("Content-Type", APPLICATION_JSON)
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PING.name().toLowerCase())
Comment thread
nevingeorgesunny marked this conversation as resolved.
Outdated
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_OK));
}

@Test
void shouldReturnErrOnEmptyPayloadAndHeader() throws Exception {
given().spec(spec)
.log().all()
.expect().log().all()
.statusCode(SC_BAD_REQUEST)
.body(containsString("Hook should contain event type"))
.request()
.post(getPath());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.noBody())
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_BAD_REQUEST));
assertThat("body", response.body(), containsString("Hook should contain event type"));
}

@Test
void shouldReturnErrOnEmptyPayload() throws Exception {
given().spec(spec)
.header(eventHeader(GHEvent.PUSH))
.log().all()
.expect().log().all()
.statusCode(SC_BAD_REQUEST)
.body(containsString("Hook should contain payload"))
.request()
.post(getPath());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.noBody())
.header(GHEventHeader.PayloadHandler.EVENT_HEADER, GHEvent.PUSH.name().toLowerCase())
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_BAD_REQUEST));
assertThat("body", response.body(), containsString("Hook should contain payload"));
}

@Test
void shouldReturnErrOnGetReq() throws Exception {
given().spec(spec)
.log().all().expect().log().all()
.statusCode(SC_METHOD_NOT_ALLOWED)
.request()
.get(getPath());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.GET()
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_METHOD_NOT_ALLOWED));
}

@Test
void shouldProcessSelfTest() throws Exception {
given().spec(spec)
.header(new Header(GitHubWebHook.URL_VALIDATION_HEADER, NOT_NULL_VALUE))
.log().all()
.expect().log().all()
.statusCode(SC_OK)
.header(GitHubWebHook.X_INSTANCE_IDENTITY, notNullValue())
.request()
.post(getPath());
}

public Header eventHeader(GHEvent event) {
return eventHeader(event.name().toLowerCase());
HttpResponse<String> response = httpClient.send(
HttpRequest.newBuilder(URI.create(getPath()))
.POST(HttpRequest.BodyPublishers.noBody())
.header(GitHubWebHook.URL_VALIDATION_HEADER, "nonnull")
.build(),
HttpResponse.BodyHandlers.ofString());
assertThat("status", response.statusCode(), is(SC_OK));
assertThat("identity header", response.headers().firstValue(GitHubWebHook.X_INSTANCE_IDENTITY).orElse(null), notNullValue());
}

public Header eventHeader(String event) {
return new Header(GHEventHeader.PayloadHandler.EVENT_HEADER, event);
private String getPath() {
return jenkins.getInstance().getRootUrl() + GitHubWebHook.URLNAME.concat("/");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gratuitous diff: moved from previous location.


public static String classpath(String path) {
Expand All @@ -185,8 +178,4 @@ public static String classpath(Class<?> clazz, String path) {
throw new RuntimeException(format("Can't load %s for class %s", path, clazz), e);
}
}

private String getPath(){
return jenkins.getInstance().getRootUrl() + GitHubWebHook.URLNAME.concat("/");
}
}