Skip to content

feat: Add updateTeamPermissions for TeamClient #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
54 changes: 41 additions & 13 deletions src/main/java/com/spotify/github/v3/clients/TeamClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
* -/-/-
*/


package com.spotify.github.v3.clients;

import static com.spotify.github.v3.clients.GitHubClient.*;
Expand All @@ -28,8 +27,10 @@
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.ImmutableTeamRepoPermissionUpdate;
import com.spotify.github.v3.orgs.requests.MembershipCreate;
import com.spotify.github.v3.orgs.requests.TeamCreate;
import com.spotify.github.v3.orgs.requests.TeamRepoPermissionUpdate;
import com.spotify.github.v3.orgs.requests.TeamUpdate;
import java.lang.invoke.MethodHandles;
import java.util.Iterator;
Expand All @@ -54,6 +55,8 @@ public class TeamClient {

private static final String INVITATIONS_TEMPLATE = "/orgs/%s/teams/%s/invitations";

private static final String REPO_TEMPLATE = "/orgs/%s/teams/%s/repos/%s/%s";

private final GitHubClient github;

private final String org;
Expand All @@ -75,7 +78,7 @@ static TeamClient create(final GitHubClient github, final String org) {
*/
public CompletableFuture<Team> createTeam(final TeamCreate request) {
final String path = String.format(TEAM_TEMPLATE, org);
log.debug("Creating team in: " + path);
log.debug("Creating team in: {}", path);
return github.post(path, github.json().toJsonUnchecked(request), Team.class);
}

Expand All @@ -87,7 +90,7 @@ public CompletableFuture<Team> createTeam(final TeamCreate request) {
*/
public CompletableFuture<Team> getTeam(final String slug) {
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
log.debug("Fetching team from " + path);
log.debug("Fetching team from {}", path);
return github.request(path, Team.class);
}

Expand All @@ -98,7 +101,7 @@ public CompletableFuture<Team> getTeam(final String slug) {
*/
public CompletableFuture<List<Team>> listTeams() {
final String path = String.format(TEAM_TEMPLATE, org);
log.debug("Fetching teams from " + path);
log.debug("Fetching teams from {}", path);
return github.request(path, LIST_TEAMS);
}

Expand All @@ -111,7 +114,7 @@ public CompletableFuture<List<Team>> listTeams() {
*/
public CompletableFuture<Team> updateTeam(final TeamUpdate request, final String slug) {
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
log.debug("Updating team in: " + path);
log.debug("Updating team in: {}", path);
return github.patch(path, github.json().toJsonUnchecked(request), Team.class);
}

Expand All @@ -123,7 +126,7 @@ public CompletableFuture<Team> updateTeam(final TeamUpdate request, final String
*/
public CompletableFuture<Void> deleteTeam(final String slug) {
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
log.debug("Deleting team from: " + path);
log.debug("Deleting team from: {}", path);
return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER);
}

Expand All @@ -133,9 +136,10 @@ public CompletableFuture<Void> deleteTeam(final String slug) {
* @param request update membership request
* @return membership
*/
public CompletableFuture<Membership> updateMembership(final MembershipCreate request, final String slug, final String username) {
public CompletableFuture<Membership> 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);
log.debug("Updating membership in: {}", path);
return github.put(path, github.json().toJsonUnchecked(request), Membership.class);
}

Expand All @@ -148,7 +152,7 @@ public CompletableFuture<Membership> updateMembership(final MembershipCreate req
*/
public CompletableFuture<Membership> getMembership(final String slug, final String username) {
final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username);
log.debug("Fetching membership for: " + path);
log.debug("Fetching membership for: {}", path);
return github.request(path, Membership.class);
}

Expand All @@ -160,7 +164,7 @@ public CompletableFuture<Membership> getMembership(final String slug, final Stri
*/
public CompletableFuture<List<User>> listTeamMembers(final String slug) {
final String path = String.format(MEMBERS_TEMPLATE, org, slug);
log.debug("Fetching members for: " + path);
log.debug("Fetching members for: {}", path);
return github.request(path, LIST_TEAM_MEMBERS);
}

Expand All @@ -173,7 +177,7 @@ public CompletableFuture<List<User>> listTeamMembers(final String slug) {
*/
public Iterator<AsyncPage<User>> listTeamMembers(final String slug, final int pageSize) {
final String path = String.format(PAGED_MEMBERS_TEMPLATE, org, slug, pageSize);
log.debug("Fetching members for: " + path);
log.debug("Fetching members for: {}", path);
return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_TEAM_MEMBERS));
}

Expand All @@ -185,7 +189,7 @@ public Iterator<AsyncPage<User>> listTeamMembers(final String slug, final int pa
*/
public CompletableFuture<Void> deleteMembership(final String slug, final String username) {
final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username);
log.debug("Deleting membership from: " + path);
log.debug("Deleting membership from: {}", path);
return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER);
}

Expand All @@ -197,7 +201,31 @@ public CompletableFuture<Void> deleteMembership(final String slug, final String
*/
public CompletableFuture<List<TeamInvitation>> listPendingTeamInvitations(final String slug) {
final String path = String.format(INVITATIONS_TEMPLATE, org, slug);
log.debug("Fetching pending invitations for: " + path);
log.debug("Fetching pending invitations for: {}", path);
return github.request(path, LIST_PENDING_TEAM_INVITATIONS);
}

/**
* Update permissions for a team on a specific repository.
*
* @param slug the team slug
* @param repo the repository name
* @param permission the permission level (pull, push, maintain, triage, admin, or a custom repo defined role name)
* @return void status code 204 if successful
*/
public CompletableFuture<Void> updateTeamPermissions(
final String slug, final String repo, final String permission) {
final String path = String.format(REPO_TEMPLATE, org, slug, org, repo);
final TeamRepoPermissionUpdate request =
ImmutableTeamRepoPermissionUpdate.builder()
.org(org)
.repo(repo)
.teamSlug(slug)
.permission(permission)
.build();
log.debug("Updating team permissions for: {}", path);
return github
.put(path, github.json().toJsonUnchecked(request))
.thenAccept(IGNORE_RESPONSE_CONSUMER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*-
* -\-\-
* 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 org.immutables.value.Value;

/** Request to update permissions of a team for a specific repo */
@Value.Immutable
@GithubStyle
@JsonSerialize(as = ImmutableTeamRepoPermissionUpdate.class)
@JsonDeserialize(as = ImmutableTeamRepoPermissionUpdate.class)
public interface TeamRepoPermissionUpdate {
String org();
String repo();
String teamSlug();
String permission();
}
36 changes: 29 additions & 7 deletions src/test/java/com/spotify/github/v3/clients/TeamClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package com.spotify.github.v3.clients;

import static com.google.common.io.Resources.getResource;
import static com.spotify.github.MockHelper.createMockHttpResponse;
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;
Expand Down Expand Up @@ -48,6 +49,7 @@
import com.spotify.github.v3.orgs.requests.TeamUpdate;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -135,7 +137,10 @@ public void updateTeam() throws Exception {

assertThat(actualResponse.get().name(), is("Justice League2"));
verify(github, times(1))
.patch(eq("/orgs/github/teams/justice-league"), eq("{\"name\":\"Justice League2\"}"), eq(Team.class));
.patch(
eq("/orgs/github/teams/justice-league"),
eq("{\"name\":\"Justice League2\"}"),
eq(Team.class));
}

@Test
Expand Down Expand Up @@ -165,22 +170,24 @@ public void listTeamMembers() throws Exception {
@Test
public void listTeamMembersPaged() throws Exception {
final String firstPageLink =
"<https://github.com/api/v3/orgs/github/teams/1/members?page=2>; rel=\"next\", <https://github.com/api/v3/orgs/github/teams/1/members?page=2>; rel=\"last\"";
"<https://github.com/api/v3/orgs/github/teams/1/members?page=2>; rel=\"next\", <https://github.com/api/v3/orgs/github/teams/1/members?page=2>; rel=\"last\"";
final String firstPageBody =
Resources.toString(getResource(this.getClass(), "list_members_page1.json"), defaultCharset());
Resources.toString(
getResource(this.getClass(), "list_members_page1.json"), defaultCharset());
final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody);

final String lastPageLink =
"<https://github.com/api/v3/orgs/github/teams/1/members>; rel=\"first\", <https://github.com/api/v3/orgs/github/teams/1/members>; rel=\"prev\"";
"<https://github.com/api/v3/orgs/github/teams/1/members>; rel=\"first\", <https://github.com/api/v3/orgs/github/teams/1/members>; rel=\"prev\"";
final String lastPageBody =
Resources.toString(getResource(this.getClass(), "list_members_page2.json"), defaultCharset());
Resources.toString(
getResource(this.getClass(), "list_members_page2.json"), defaultCharset());

final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody);

when(github.request(endsWith("/orgs/github/teams/1/members?per_page=1")))
.thenReturn(completedFuture(firstPageResponse));
.thenReturn(completedFuture(firstPageResponse));
when(github.request(endsWith("/orgs/github/teams/1/members?page=2")))
.thenReturn(completedFuture(lastPageResponse));
.thenReturn(completedFuture(lastPageResponse));

final Iterable<AsyncPage<User>> pageIterator = () -> teamClient.listTeamMembers("1", 1);
final List<User> users = Async.streamFromPaginatingIterable(pageIterator).collect(toList());
Expand Down Expand Up @@ -229,4 +236,19 @@ public void listPendingTeamInvitations() throws Exception {
assertThat(pendingInvitations.get(1).id(), is(2));
assertThat(pendingInvitations.size(), is(2));
}

@Test
void updateTeamPermissions() {
String apiUrl = "/orgs/github/teams/cat-squad/repos/github/octocat";
HttpResponse response = createMockHttpResponse(apiUrl, 204, "", Map.of());
when(github.put(eq(apiUrl), any())).thenReturn(completedFuture(response));

teamClient.updateTeamPermissions("cat-squad", "octocat", "pull").join();

verify(github, times(1))
.put(
eq(apiUrl),
eq(
"{\"org\":\"github\",\"repo\":\"octocat\",\"team_slug\":\"cat-squad\",\"permission\":\"pull\"}"));
}
}