diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f07f445..7544666b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: java-version: 11 distribution: corretto diff --git a/pom.xml b/pom.xml index 02c1bc9a..344302b4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 github-client - 0.2.18-SNAPSHOT + 0.3.2-SNAPSHOT com.spotify @@ -84,7 +84,7 @@ UTF-8 UTF-8 - 1710939443 + 1720619003 spotbugsexclude.xml error checkstyle.xml diff --git a/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java b/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java index 9198fc60..e9150d96 100644 --- a/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java @@ -45,6 +45,7 @@ public class GithubAppClient { refer to the organisation in the installation endpoint */ private static final String GET_INSTALLATION_ORG_URL = "/orgs/%s/installation"; + private static final String GET_INSTALLATION_USER_URL = "/users/%s/installation"; private final GitHubClient github; private final String owner; @@ -78,7 +79,7 @@ public CompletableFuture> getInstallations() { } /** - * Get Installation + * Get Installation of repo or org * * @return an Installation */ @@ -114,6 +115,15 @@ private CompletableFuture getOrgInstallation() { String.format(GET_INSTALLATION_ORG_URL, owner), Installation.class); } + /** + * Get an installation of a user + * @return an Installation + */ + public CompletableFuture getUserInstallation() { + return github.request( + String.format(GET_INSTALLATION_USER_URL, owner), Installation.class); + } + /** * Authenticates as an installation * diff --git a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java index 97fd5bc1..c2c49c04 100644 --- a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java +++ b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java @@ -85,6 +85,7 @@ public class RepositoryClient { private static final String BRANCH_TEMPLATE = "/repos/%s/%s/branches/%s"; private static final String LIST_BRANCHES_TEMPLATE = "/repos/%s/%s/branches"; private static final String CREATE_COMMENT_TEMPLATE = "/repos/%s/%s/commits/%s/comments"; + private static final String CREATE_REPOSITORY_DISPATCH_EVENT_TEMPLATE = "/repos/%s/%s/dispatches"; private static final String COMMENT_TEMPLATE = "/repos/%s/%s/comments/%s"; private static final String LANGUAGES_TEMPLATE = "/repos/%s/%s/languages"; private static final String MERGE_TEMPLATE = "/repos/%s/%s/merges"; @@ -698,4 +699,17 @@ private String getContentPath(final String path, final String query) { } return String.format(CONTENTS_URI_TEMPLATE, owner, repo, path, query); } + + /** + * Create a repository_dispatch event. + * + * @param request The repository dispatch request. + */ + + public CompletableFuture createRepositoryDispatchEvent(final RepositoryDispatch request) { + final String path = String.format(CREATE_REPOSITORY_DISPATCH_EVENT_TEMPLATE, owner, repo); + return github + .post(path, github.json().toJsonUnchecked(request)) + .thenApply(response -> response.code() == NO_CONTENT); //should always return a 204 + } } diff --git a/src/main/java/com/spotify/github/v3/clients/UserClient.java b/src/main/java/com/spotify/github/v3/clients/UserClient.java index 3ea24979..0d48e027 100644 --- a/src/main/java/com/spotify/github/v3/clients/UserClient.java +++ b/src/main/java/com/spotify/github/v3/clients/UserClient.java @@ -27,15 +27,21 @@ public class UserClient { public static final int NO_CONTENT = 204; private final GitHubClient github; + private final String owner; private static final String SUSPEND_USER_TEMPLATE = "/users/%s/suspended"; - UserClient(final GitHubClient github) { + UserClient(final GitHubClient github, final String owner) { this.github = github; + this.owner = owner; } - static UserClient create(final GitHubClient github) { - return new UserClient(github); + static UserClient create(final GitHubClient github, final String owner) { + return new UserClient(github, owner); + } + + public GithubAppClient createGithubAppClient() { + return new GithubAppClient(this.github, this.owner); } /** diff --git a/src/main/java/com/spotify/github/v3/repos/requests/RepositoryDispatch.java b/src/main/java/com/spotify/github/v3/repos/requests/RepositoryDispatch.java new file mode 100644 index 00000000..5c7c79e1 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/repos/requests/RepositoryDispatch.java @@ -0,0 +1,44 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2023 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.repos.requests; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.util.Optional; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableRepositoryDispatch.class) +@JsonDeserialize(as = ImmutableRepositoryDispatch.class) +public interface RepositoryDispatch { + + /** The custom webhook event name */ + + String eventType(); + + /** JSON payload with extra information about the webhook event + * that your action or workflow may use. */ + Optional clientPayload(); + +} diff --git a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java index 128ed61b..c9d24a1e 100644 --- a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java @@ -42,6 +42,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.Resources; @@ -73,6 +75,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter; @@ -722,4 +725,26 @@ public void shouldReturnEmptyOptionalWhenResponseBodyNotPresent() throws Excepti Optional response = repoClient.downloadZipball("master").get(); assertThat(response, is(Optional.empty())); } + + @Test + public void shouldReturnEmptyResponseWhenRepositoryDispatchEndpointTriggered() throws Exception { + final Response response = mock(Response.class); + when(response.code()).thenReturn(204); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode clientPayload = mapper.createObjectNode(); + clientPayload.put("my-custom-true-property","true"); + clientPayload.put("my-custom-false-property", "false"); + + RepositoryDispatch repositoryDispatchRequest = ImmutableRepositoryDispatch.builder() + .eventType("my-custom-event") + .clientPayload(clientPayload) + .build(); + + when(github.post("/repos/someowner/somerepo/dispatches", json.toJsonUnchecked(repositoryDispatchRequest))).thenReturn(completedFuture(response)); + + boolean repoDispatchResult = repoClient.createRepositoryDispatchEvent(repositoryDispatchRequest).get(); + assertTrue(repoDispatchResult); + } + } diff --git a/src/test/java/com/spotify/github/v3/clients/UserClientTest.java b/src/test/java/com/spotify/github/v3/clients/UserClientTest.java index 0211769b..ce8e7259 100644 --- a/src/test/java/com/spotify/github/v3/clients/UserClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/UserClientTest.java @@ -19,16 +19,25 @@ */ package com.spotify.github.v3.clients; +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.google.common.io.Resources; import com.spotify.github.jackson.Json; +import com.spotify.github.v3.checks.Installation; import com.spotify.github.v3.user.requests.ImmutableSuspensionReason; + +import java.io.IOException; import java.util.concurrent.CompletableFuture; + import okhttp3.Response; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,12 +46,17 @@ public class UserClientTest { private GitHubClient github; private UserClient userClient; + private String owner = "github"; + private Json json; + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); + } @BeforeEach public void setUp() { github = mock(GitHubClient.class); - userClient = new UserClient(github); - Json json = Json.create(); + userClient = new UserClient(github, owner); + json = Json.create(); when(github.json()).thenReturn(json); } @@ -81,4 +95,17 @@ public void testUnSuspendUserFailure() throws Exception { final CompletableFuture result = userClient.unSuspendUser("username", ImmutableSuspensionReason.builder().reason("That's why").build()); assertFalse(result.get()); } + + @Test + public void testAppClient() throws Exception { + final GithubAppClient githubAppClient = userClient.createGithubAppClient(); + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("../githubapp/installation.json"), Installation.class)); + when(github.request("/users/github/installation", Installation.class)).thenReturn(fixture); + + final Installation installation = githubAppClient.getUserInstallation().get(); + + assertThat(installation.id(), is(1)); + assertThat(installation.account().login(), is("github")); + } }