diff --git a/README.md b/README.md index d7b15091..da5f3c24 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ final RepositoryClient repositoryClient = githubClient.createRepositoryClient("m log.info(repositoryClient.getCommit("sha").get().htmlUrl()); ``` -Some APIs, such as Checks API are nested in the Repository API. Endpoints such as `POST /repos/:owner/:repo/check-runs` live in the ChecksClient: - +Another example of the mirrored structure is that some of the APIs are nested under a parent API. +For example, endpoints related to check runs or issues are nested under the Repository client: ```java final ChecksClient checksClient = repositoryClient.createChecksApiClient(); checksClient.createCheckRun(CHECK_RUN_REQUEST); @@ -76,8 +76,15 @@ checksClient.createCheckRun(CHECK_RUN_REQUEST); final IssueClient issueClient = repositoryClient.createIssueClient(); issueClient.createComment(ISSUE_ID, "comment body") .thenAccept(comment -> log.info("created comment " + comment.htmlUrl())); + ``` +And endpoints related to teams and memberships are nested under the Organisation client: +```java +final TeamClient teamClient = organisationClient.createTeamClient(); + teamClient.getMembership("username"); +``` + ## Contributing This project uses Maven. To run the tests locally, just run: diff --git a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java index 0bd9283a..28ac3eb2 100644 --- a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java @@ -27,11 +27,13 @@ import com.spotify.github.Tracer; import com.spotify.github.jackson.Json; import com.spotify.github.v3.Team; +import com.spotify.github.v3.User; import com.spotify.github.v3.checks.AccessToken; import com.spotify.github.v3.comment.Comment; import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException; import com.spotify.github.v3.exceptions.RequestNotOkException; import com.spotify.github.v3.git.Reference; +import com.spotify.github.v3.orgs.TeamInvitation; import com.spotify.github.v3.prs.PullRequestItem; import com.spotify.github.v3.prs.Review; import com.spotify.github.v3.prs.ReviewRequests; @@ -101,6 +103,12 @@ public class GitHubClient { static final TypeReference> LIST_TEAMS = new TypeReference<>() {}; + static final TypeReference> LIST_TEAM_MEMBERS = + new TypeReference<>() {}; + + static final TypeReference> LIST_PENDING_TEAM_INVITATIONS = + new TypeReference<>() {}; + private static final String GET_ACCESS_TOKEN_URL = "app/installations/%s/access_tokens"; private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java b/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java index ea5f9197..84620411 100644 --- a/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java +++ b/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java @@ -20,14 +20,7 @@ // package com.spotify.github.v3.clients; -import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; -import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; - -import com.spotify.github.v3.Team; -import com.spotify.github.v3.orgs.requests.TeamCreate; import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.CompletableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,62 +46,11 @@ static OrganisationClient create(final GitHubClient github, final String org) { } /** - * Create a team in an organisation. - * - * @param request create team request - * @return team - */ - public CompletableFuture createTeam(final TeamCreate request) { - final String path = String.format(TEAM_TEMPLATE, org); - log.debug("Creating team in: " + path); - return github.post(path, github.json().toJsonUnchecked(request), Team.class); - } - - /** - * Get a specific team in an organisation. - * - * @param slug slug of the team name - * @return team - */ - public CompletableFuture getTeam(final String slug) { - final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Fetching team from " + path); - return github.request(path, Team.class); - } - - /** - * List teams within an organisation. - * - * @return list of all teams in an organisation - */ - public CompletableFuture> listTeams() { - final String path = String.format(TEAM_TEMPLATE, org); - log.debug("Fetching teams from " + path); - return github.request(path, LIST_TEAMS); - } - - /** - * Update a team in an organisation. - * - * @param request update team request - * @param slug slug of the team name - * @return team - */ - public CompletableFuture updateTeam(final TeamCreate request, final String slug) { - final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Updating team in: " + path); - return github.patch(path, github.json().toJsonUnchecked(request), Team.class); - } - - /** - * Delete a specific team in an organisation. + * Create a Teams API client. * - * @param slug slug of the team name - * @return team + * @return Teams API client */ - public CompletableFuture deleteTeam(final String slug) { - final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Deleting team from: " + path); - return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + public TeamClient createTeamClient(final GitHubClient github, final String org) { + return TeamClient.create(github, org); } } diff --git a/src/main/java/com/spotify/github/v3/clients/TeamClient.java b/src/main/java/com/spotify/github/v3/clients/TeamClient.java new file mode 100644 index 00000000..6b7d1699 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/TeamClient.java @@ -0,0 +1,188 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 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.clients; + +import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PENDING_TEAM_INVITATIONS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAM_MEMBERS; + +import com.spotify.github.v3.Team; +import com.spotify.github.v3.User; +import com.spotify.github.v3.orgs.Membership; +import com.spotify.github.v3.orgs.TeamInvitation; +import com.spotify.github.v3.orgs.requests.MembershipCreate; +import com.spotify.github.v3.orgs.requests.TeamCreate; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TeamClient { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String TEAM_TEMPLATE = "/orgs/%s/teams"; + + private static final String TEAM_SLUG_TEMPLATE = "/orgs/%s/teams/%s"; + + private static final String MEMBERS_TEMPLATE = "/orgs/%s/teams/%s/members"; + + private static final String MEMBERSHIP_TEMPLATE = "/orgs/%s/teams/%s/memberships/%s"; + + private static final String INVITATIONS_TEMPLATE = "/orgs/%s/teams/%s/invitations"; + + private final GitHubClient github; + + private final String org; + + TeamClient(final GitHubClient github, final String org) { + this.github = github; + this.org = org; + } + + static TeamClient create(final GitHubClient github, final String org) { + return new TeamClient(github, org); + } + + /** + * Create a team in an organisation. + * + * @param request create team request + * @return team + */ + public CompletableFuture createTeam(final TeamCreate request) { + final String path = String.format(TEAM_TEMPLATE, org); + log.debug("Creating team in: " + path); + return github.post(path, github.json().toJsonUnchecked(request), Team.class); + } + + /** + * Get a specific team in an organisation. + * + * @param slug slug of the team name + * @return team + */ + public CompletableFuture getTeam(final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Fetching team from " + path); + return github.request(path, Team.class); + } + + /** + * List teams within an organisation. + * + * @return list of all teams in an organisation + */ + public CompletableFuture> listTeams() { + final String path = String.format(TEAM_TEMPLATE, org); + log.debug("Fetching teams from " + path); + return github.request(path, LIST_TEAMS); + } + + /** + * Update a team in an organisation. + * + * @param request update team request + * @param slug slug of the team name + * @return team + */ + public CompletableFuture updateTeam(final TeamCreate request, final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Updating team in: " + path); + return github.patch(path, github.json().toJsonUnchecked(request), Team.class); + } + + /** + * Delete a specific team in an organisation. + * + * @param slug slug of the team name + * @return team + */ + public CompletableFuture deleteTeam(final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Deleting team from: " + path); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + /** + * Add or update a team membership for a user. + * + * @param request update membership request + * @return membership + */ + public CompletableFuture updateMembership(final MembershipCreate request, final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Updating membership in: " + path); + return github.put(path, github.json().toJsonUnchecked(request), Membership.class); + } + + /** + * Get a team membership of a user. + * + * @param slug the team slug + * @param username username of the team member + * @return membership + */ + public CompletableFuture getMembership(final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Fetching membership for: " + path); + return github.request(path, Membership.class); + } + + /** + * List members of a specific team. + * + * @param slug the team slug + * @return list of all users in a team + */ + public CompletableFuture> listTeamMembers(final String slug) { + final String path = String.format(MEMBERS_TEMPLATE, org, slug); + log.debug("Fetching members for: " + path); + return github.request(path, LIST_TEAM_MEMBERS); + } + + /** + * Delete a membership for a user. + * + * @param slug slug of the team name + * @return membership + */ + public CompletableFuture deleteMembership(final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Deleting membership from: " + path); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + /** + * List pending invitations for a team. + * + * @param slug the team slug + * @return list of pending invitations for a team + */ + public CompletableFuture> listPendingTeamInvitations(final String slug) { + final String path = String.format(INVITATIONS_TEMPLATE, org, slug); + log.debug("Fetching pending invitations for: " + path); + return github.request(path, LIST_PENDING_TEAM_INVITATIONS); + } +} diff --git a/src/main/java/com/spotify/github/v3/orgs/Membership.java b/src/main/java/com/spotify/github/v3/orgs/Membership.java new file mode 100644 index 00000000..17c190c1 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/Membership.java @@ -0,0 +1,50 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 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.orgs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.net.URI; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** + * Membership resource represents data returned by a single Membership get operation. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMembership.class) +@JsonDeserialize(as = ImmutableMembership.class) +public interface Membership { + + /** URL */ + @Nullable + URI url(); + + /** ROLE */ + @Nullable + String role(); + + /** STATE */ + @Nullable + String state(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java b/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java new file mode 100644 index 00000000..a58c12b6 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java @@ -0,0 +1,76 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 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.orgs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.v3.User; +import java.net.URI; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamInvitation.class) +@JsonDeserialize(as = ImmutableTeamInvitation.class) +public interface TeamInvitation { + /** ID */ + @Nullable + Integer id(); + + /** login username */ + @Nullable + String login(); + + /** Node ID */ + @Nullable + String nodeId(); + + /** Email address */ + @Nullable + String email(); + + /** Role */ + @Nullable + String role(); + + /** Failed reason */ + @Nullable + String failedReason(); + + /** Inviter */ + + @Nullable + User inviter(); + + /** Team Count */ + @Nullable + Integer teamCount(); + + /** Invitation Teams URL */ + @Nullable + URI invitationTeamsUrl(); + + /** Invitation Source */ + @Nullable + String invitationSource(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java b/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java new file mode 100644 index 00000000..cc6fc100 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java @@ -0,0 +1,42 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 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.orgs.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** Request to create a team within a given organisation */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMembershipCreate.class) +@JsonDeserialize(as = ImmutableMembershipCreate.class) +public interface MembershipCreate { + + /** + * The role that this user should have in the team. + * Defaults to 'member' + */ + @Nullable + String role(); +} diff --git a/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java b/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java index 6779179e..9f0645ca 100644 --- a/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java @@ -21,35 +21,21 @@ package com.spotify.github.v3.clients; import static com.google.common.io.Resources.getResource; -import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; 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.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.Team; -import com.spotify.github.v3.orgs.requests.TeamCreate; import java.io.IOException; -import java.util.List; import java.util.concurrent.CompletableFuture; -import okhttp3.Headers; -import okhttp3.Response; -import okhttp3.ResponseBody; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -@RunWith(PowerMockRunner.class) -@PrepareForTest({ Headers.class, ResponseBody.class, Response.class}) public class OrganisationClientTest { private GitHubClient github; @@ -59,7 +45,7 @@ public class OrganisationClientTest { private Json json; private static String getFixture(String resource) throws IOException { - return Resources.toString(getResource(OrganisationClientTest.class, resource), defaultCharset()); + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); } @Before @@ -71,66 +57,13 @@ public void setUp() { } @Test - public void getTeam() throws Exception { + public void testTeamClient() throws Exception { + final TeamClient teamClient = organisationClient.createTeamClient(github, "github"); final CompletableFuture fixture = completedFuture(json.fromJson(getFixture("team_get.json"), Team.class)); when(github.request("/orgs/github/teams/justice-league", Team.class)).thenReturn(fixture); - final Team team = organisationClient.getTeam("justice-league").get(); + final Team team = teamClient.getTeam("justice-league").get(); assertThat(team.id(), is(1)); assertThat(team.name(), is("Justice League")); } - - @Test - public void listTeams() throws Exception { - final CompletableFuture> fixture = - completedFuture(json.fromJson(getFixture("teams_list.json"), LIST_TEAMS)); - when(github.request("/orgs/github/teams", LIST_TEAMS)).thenReturn(fixture); - final List teams = organisationClient.listTeams().get(); - assertThat(teams.get(0).slug(), is("justice-league")); - assertThat(teams.get(1).slug(), is("x-men")); - assertThat(teams.size(), is(2)); - } - - @Test - public void deleteTeam() throws Exception { - final CompletableFuture response = completedFuture(mock(Response.class)); - final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); - when(github.delete(capture.capture())).thenReturn(response); - - CompletableFuture deleteResponse = organisationClient.deleteTeam("justice-league"); - deleteResponse.get(); - assertThat(capture.getValue(), is("/orgs/github/teams/justice-league")); - } - - @Test - public void createTeam() throws Exception { - final TeamCreate teamCreateRequest = - json.fromJson( - getFixture("teams_request.json"), - TeamCreate.class); - - final CompletableFuture fixtureResponse = completedFuture(json.fromJson( - getFixture("team_get.json"), - Team.class)); - when(github.post(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); - final CompletableFuture actualResponse = organisationClient.createTeam(teamCreateRequest); - - assertThat(actualResponse.get().name(), is("Justice League")); - } - - @Test - public void updateTeam() throws Exception { - final TeamCreate teamCreateRequest = - json.fromJson( - getFixture("teams_patch.json"), - TeamCreate.class); - - final CompletableFuture fixtureResponse = completedFuture(json.fromJson( - getFixture("teams_patch_response.json"), - Team.class)); - when(github.patch(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); - final CompletableFuture actualResponse = organisationClient.updateTeam(teamCreateRequest, "justice-league"); - - assertThat(actualResponse.get().name(), is("Justice League2")); - } } diff --git a/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java new file mode 100644 index 00000000..bb9bcf9c --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java @@ -0,0 +1,203 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 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.clients; + +import static com.google.common.io.Resources.getResource; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PENDING_TEAM_INVITATIONS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAM_MEMBERS; +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.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.Team; +import com.spotify.github.v3.User; +import com.spotify.github.v3.orgs.Membership; +import com.spotify.github.v3.orgs.TeamInvitation; +import com.spotify.github.v3.orgs.requests.MembershipCreate; +import com.spotify.github.v3.orgs.requests.TeamCreate; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import okhttp3.Headers; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Headers.class, ResponseBody.class, Response.class}) +public class TeamClientTest { + + private GitHubClient github; + + private TeamClient teamClient; + + private Json json; + + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); + } + + @Before + public void setUp() { + github = mock(GitHubClient.class); + teamClient = new TeamClient(github, "github"); + json = Json.create(); + when(github.json()).thenReturn(json); + } + + @Test + public void getTeam() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("team_get.json"), Team.class)); + when(github.request("/orgs/github/teams/justice-league", Team.class)).thenReturn(fixture); + final Team team = teamClient.getTeam("justice-league").get(); + assertThat(team.id(), is(1)); + assertThat(team.name(), is("Justice League")); + } + + @Test + public void listTeams() throws Exception { + final CompletableFuture> fixture = + completedFuture(json.fromJson(getFixture("teams_list.json"), LIST_TEAMS)); + when(github.request("/orgs/github/teams", LIST_TEAMS)).thenReturn(fixture); + final List teams = teamClient.listTeams().get(); + assertThat(teams.get(0).slug(), is("justice-league")); + assertThat(teams.get(1).slug(), is("x-men")); + assertThat(teams.size(), is(2)); + } + + @Test + public void deleteTeam() throws Exception { + final CompletableFuture response = completedFuture(mock(Response.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = teamClient.deleteTeam("justice-league"); + deleteResponse.get(); + assertThat(capture.getValue(), is("/orgs/github/teams/justice-league")); + } + + @Test + public void createTeam() throws Exception { + final TeamCreate teamCreateRequest = + json.fromJson( + getFixture("teams_request.json"), + TeamCreate.class); + + final CompletableFuture fixtureResponse = completedFuture(json.fromJson( + getFixture("team_get.json"), + Team.class)); + when(github.post(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = teamClient.createTeam(teamCreateRequest); + + assertThat(actualResponse.get().name(), is("Justice League")); + } + + @Test + public void updateTeam() throws Exception { + final TeamCreate teamCreateRequest = + json.fromJson( + getFixture("teams_patch.json"), + TeamCreate.class); + + final CompletableFuture fixtureResponse = completedFuture(json.fromJson( + getFixture("teams_patch_response.json"), + Team.class)); + when(github.patch(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = teamClient.updateTeam(teamCreateRequest, "justice-league"); + + assertThat(actualResponse.get().name(), is("Justice League2")); + } + + @Test + public void getMembership() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("membership.json"), Membership.class)); + when(github.request("/orgs/github/teams/1/memberships/octocat", Membership.class)).thenReturn(fixture); + final Membership membership = teamClient.getMembership("1", "octocat").get(); + assertThat(membership.url().toString(), is("https://api.github.com/teams/1/memberships/octocat")); + assertThat(membership.role(), is("maintainer")); + assertThat(membership.state(), is("active")); + } + + @Test + public void listTeamMembers() throws Exception { + final CompletableFuture> fixture = + completedFuture(json.fromJson(getFixture("list_members.json"), LIST_TEAM_MEMBERS)); + when(github.request("/orgs/github/teams/1/members", LIST_TEAM_MEMBERS)).thenReturn(fixture); + final List teamMembers = teamClient.listTeamMembers("1").get(); + assertThat(teamMembers.get(0).login(), is("octocat")); + assertThat(teamMembers.get(1).id(), is(2)); + assertThat(teamMembers.size(), is(2)); + } + + @Test + public void updateMembership() throws Exception { + final MembershipCreate membershipCreateRequest = + json.fromJson( + getFixture("membership_update.json"), + MembershipCreate.class); + + final CompletableFuture fixtureResponse = completedFuture(json.fromJson( + getFixture("membership_update_response.json"), + Membership.class)); + when(github.put(any(), any(), eq(Membership.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = teamClient.updateMembership(membershipCreateRequest, "1", "octocat"); + + assertThat(actualResponse.get().role(), is("member")); + } + + @Test + public void deleteMembership() throws Exception { + final CompletableFuture response = completedFuture(mock(Response.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = teamClient.deleteMembership("1", "octocat"); + deleteResponse.get(); + assertThat(capture.getValue(), is("/orgs/github/teams/1/memberships/octocat")); + } + + @Test + public void listPendingTeamInvitations() throws Exception { + final CompletableFuture> fixture = + completedFuture(json.fromJson(getFixture("list_team_invitations.json"), LIST_PENDING_TEAM_INVITATIONS)); + when(github.request("/orgs/github/teams/1/invitations", LIST_PENDING_TEAM_INVITATIONS)).thenReturn(fixture); + final List pendingInvitations = teamClient.listPendingTeamInvitations("1").get(); + assertThat(pendingInvitations.get(0).login(), is("octocat")); + assertThat(pendingInvitations.get(1).id(), is(2)); + assertThat(pendingInvitations.size(), is(2)); + } +} diff --git a/src/test/resources/com/spotify/github/v3/clients/list_members.json b/src/test/resources/com/spotify/github/v3/clients/list_members.json new file mode 100644 index 00000000..f8563088 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_members.json @@ -0,0 +1,42 @@ +[ + { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "octocat2", + "id": 2, + "node_id": "MDQ2VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat2_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat2", + "html_url": "https://github.com/octocat2", + "followers_url": "https://api.github.com/users/octocat2/followers", + "following_url": "https://api.github.com/users/octoca2t/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat2/subscriptions", + "organizations_url": "https://api.github.com/users/octocat2/orgs", + "repos_url": "https://api.github.com/users/octocat2/repos", + "events_url": "https://api.github.com/users/octocat2/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat2/received_events", + "type": "User", + "site_admin": false + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json b/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json new file mode 100644 index 00000000..768256c7 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json @@ -0,0 +1,68 @@ +[ + { + "id": 1, + "login": "octocat", + "node_id": "MDQ6VXNlcjE=", + "email": "octocat@github.com", + "role": "direct_member", + "created_at": "2016-11-30T06:46:10-08:00", + "failed_at": "", + "failed_reason": "", + "inviter": { + "login": "other_user", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following{/other_user}", + "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", + "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events{/privacy}", + "received_events_url": "https://api.github.com/users/other_user/received_events", + "type": "User", + "site_admin": false + }, + "team_count": 2, + "invitation_teams_url": "https://api.github.com/organizations/2/invitations/1/teams", + "invitation_source": "member" + }, + { + "id": 2, + "login": "monalisa", + "node_id": "MDQ3VXNlcjE=", + "email": "octocat2@github.com", + "role": "direct_member", + "created_at": "2016-11-30T06:46:10-08:00", + "failed_at": "", + "failed_reason": "", + "inviter": { + "login": "other_user", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following{/other_user}", + "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", + "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events{/privacy}", + "received_events_url": "https://api.github.com/users/other_user/received_events", + "type": "User", + "site_admin": false + }, + "team_count": 2, + "invitation_teams_url": "https://api.github.com/organizations/2/invitations/1/teams", + "invitation_source": "member" + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership.json b/src/test/resources/com/spotify/github/v3/clients/membership.json new file mode 100644 index 00000000..fce6fa69 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership.json @@ -0,0 +1,5 @@ +{ + "url": "https://api.github.com/teams/1/memberships/octocat", + "role": "maintainer", + "state": "active" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership_update.json b/src/test/resources/com/spotify/github/v3/clients/membership_update.json new file mode 100644 index 00000000..0efabc38 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership_update.json @@ -0,0 +1,3 @@ +{ + "role": "member" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json b/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json new file mode 100644 index 00000000..0f2fd957 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json @@ -0,0 +1,5 @@ +{ + "url": "https://api.github.com/teams/1/memberships/octocat", + "role": "member", + "state": "active" +} \ No newline at end of file