Skip to content

Commit 96e7eaa

Browse files
authored
feat: Create organisation client to access teams endpoints (#135)
* feat: scaffold organisation client * feat: create get team functionality * add test for get team by slug * feat: update param naming * feat: add ability to list teams by organisation * feat: add ability to delete a team in an organisation * feat: add ability to create a team in an organisation * feat: update imports * feat: add ability to update a team in an organisation * fix: build issues * fix: formatting * feat: update comments * feat: pass org name into organisation client
1 parent e6df7c9 commit 96e7eaa

File tree

9 files changed

+525
-0
lines changed

9 files changed

+525
-0
lines changed

src/main/java/com/spotify/github/v3/clients/GitHubClient.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.fasterxml.jackson.core.type.TypeReference;
2727
import com.spotify.github.Tracer;
2828
import com.spotify.github.jackson.Json;
29+
import com.spotify.github.v3.Team;
2930
import com.spotify.github.v3.checks.AccessToken;
3031
import com.spotify.github.v3.comment.Comment;
3132
import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException;
@@ -97,6 +98,9 @@ public class GitHubClient {
9798
new TypeReference<>() {};
9899
static final TypeReference<List<RepositoryInvitation>> LIST_REPOSITORY_INVITATION = new TypeReference<>() {};
99100

101+
static final TypeReference<List<Team>> LIST_TEAMS =
102+
new TypeReference<>() {};
103+
100104
private static final String GET_ACCESS_TOKEN_URL = "app/installations/%s/access_tokens";
101105

102106
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -362,6 +366,15 @@ public ChecksClient createChecksClient(final String owner, final String repo) {
362366
return ChecksClient.create(this, owner, repo);
363367
}
364368

369+
/**
370+
* Create organisation API client
371+
*
372+
* @return organisation API client
373+
*/
374+
public OrganisationClient createOrganisationClient(final String org) {
375+
return OrganisationClient.create(this, org);
376+
}
377+
365378
Json json() {
366379
return json;
367380
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
///*-
2+
// * -\-\-
3+
// * github-api
4+
// * --
5+
// * Copyright (C) 2016 - 2020 Spotify AB
6+
// * --
7+
// * Licensed under the Apache License, Version 2.0 (the "License");
8+
// * you may not use this file except in compliance with the License.
9+
// * You may obtain a copy of the License at
10+
// *
11+
// * http://www.apache.org/licenses/LICENSE-2.0
12+
// *
13+
// * Unless required by applicable law or agreed to in writing, software
14+
// * distributed under the License is distributed on an "AS IS" BASIS,
15+
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// * See the License for the specific language governing permissions and
17+
// * limitations under the License.
18+
// * -/-/-
19+
// */
20+
//
21+
package com.spotify.github.v3.clients;
22+
23+
import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER;
24+
import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS;
25+
26+
import com.spotify.github.v3.Team;
27+
import com.spotify.github.v3.orgs.requests.TeamCreate;
28+
import java.lang.invoke.MethodHandles;
29+
import java.util.List;
30+
import java.util.concurrent.CompletableFuture;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
34+
public class OrganisationClient {
35+
36+
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
37+
38+
private static final String TEAM_TEMPLATE = "/orgs/%s/teams";
39+
40+
private static final String TEAM_SLUG_TEMPLATE = "/orgs/%s/teams/%s";
41+
42+
private final GitHubClient github;
43+
44+
private final String org;
45+
46+
OrganisationClient(final GitHubClient github, final String org) {
47+
this.github = github;
48+
this.org = org;
49+
}
50+
51+
static OrganisationClient create(final GitHubClient github, final String org) {
52+
return new OrganisationClient(github, org);
53+
}
54+
55+
/**
56+
* Create a team in an organisation.
57+
*
58+
* @param request create team request
59+
* @return team
60+
*/
61+
public CompletableFuture<Team> createTeam(final TeamCreate request) {
62+
final String path = String.format(TEAM_TEMPLATE, org);
63+
log.debug("Creating team in: " + path);
64+
return github.post(path, github.json().toJsonUnchecked(request), Team.class);
65+
}
66+
67+
/**
68+
* Get a specific team in an organisation.
69+
*
70+
* @param slug slug of the team name
71+
* @return team
72+
*/
73+
public CompletableFuture<Team> getTeam(final String slug) {
74+
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
75+
log.debug("Fetching team from " + path);
76+
return github.request(path, Team.class);
77+
}
78+
79+
/**
80+
* List teams within an organisation.
81+
*
82+
* @return list of all teams in an organisation
83+
*/
84+
public CompletableFuture<List<Team>> listTeams() {
85+
final String path = String.format(TEAM_TEMPLATE, org);
86+
log.debug("Fetching teams from " + path);
87+
return github.request(path, LIST_TEAMS);
88+
}
89+
90+
/**
91+
* Update a team in an organisation.
92+
*
93+
* @param request update team request
94+
* @param slug slug of the team name
95+
* @return team
96+
*/
97+
public CompletableFuture<Team> updateTeam(final TeamCreate request, final String slug) {
98+
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
99+
log.debug("Updating team in: " + path);
100+
return github.patch(path, github.json().toJsonUnchecked(request), Team.class);
101+
}
102+
103+
/**
104+
* Delete a specific team in an organisation.
105+
*
106+
* @param slug slug of the team name
107+
* @return team
108+
*/
109+
public CompletableFuture<Void> deleteTeam(final String slug) {
110+
final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug);
111+
log.debug("Deleting team from: " + path);
112+
return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER);
113+
}
114+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*-
2+
* -\-\-
3+
* github-api
4+
* --
5+
* Copyright (C) 2016 - 2020 Spotify AB
6+
* --
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* -/-/-
19+
*/
20+
package com.spotify.github.v3.orgs.requests;
21+
22+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
23+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
24+
import com.spotify.github.GithubStyle;
25+
import java.util.Optional;
26+
import javax.annotation.Nullable;
27+
import org.immutables.value.Value;
28+
29+
/** Request to create a team within a given organisation */
30+
@Value.Immutable
31+
@GithubStyle
32+
@JsonSerialize(as = ImmutableTeamCreate.class)
33+
@JsonDeserialize(as = ImmutableTeamCreate.class)
34+
public interface TeamCreate {
35+
36+
/** The name of the team. */
37+
@Nullable
38+
String name();
39+
40+
/** The description of the team. */
41+
Optional<String> description();
42+
43+
/**
44+
* List GitHub IDs for organization members who will
45+
* become team maintainers.
46+
*/
47+
Optional<String> maintainers();
48+
49+
/** The full name (e.g., "organization-name/repository-name")
50+
* of repositories to add the team to.
51+
*/
52+
@SuppressWarnings("checkstyle:methodname")
53+
Optional<String> repo_names();
54+
55+
/** The ID of a team to set as the parent team. */
56+
@SuppressWarnings("checkstyle:methodname")
57+
Optional<String> parent_team_id();
58+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*-
2+
* -\-\-
3+
* github-api
4+
* --
5+
* Copyright (C) 2016 - 2020 Spotify AB
6+
* --
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* -/-/-
19+
*/
20+
21+
package com.spotify.github.v3.clients;
22+
23+
import static com.google.common.io.Resources.getResource;
24+
import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS;
25+
import static java.nio.charset.Charset.defaultCharset;
26+
import static java.util.concurrent.CompletableFuture.completedFuture;
27+
import static org.hamcrest.MatcherAssert.assertThat;
28+
import static org.hamcrest.core.Is.is;
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.ArgumentMatchers.eq;
31+
import static org.mockito.Mockito.mock;
32+
import static org.mockito.Mockito.when;
33+
34+
import com.google.common.io.Resources;
35+
import com.spotify.github.jackson.Json;
36+
import com.spotify.github.v3.Team;
37+
import com.spotify.github.v3.orgs.requests.TeamCreate;
38+
import java.io.IOException;
39+
import java.util.List;
40+
import java.util.concurrent.CompletableFuture;
41+
import okhttp3.Headers;
42+
import okhttp3.Response;
43+
import okhttp3.ResponseBody;
44+
import org.junit.Before;
45+
import org.junit.Test;
46+
import org.junit.runner.RunWith;
47+
import org.mockito.ArgumentCaptor;
48+
import org.powermock.core.classloader.annotations.PrepareForTest;
49+
import org.powermock.modules.junit4.PowerMockRunner;
50+
51+
@RunWith(PowerMockRunner.class)
52+
@PrepareForTest({ Headers.class, ResponseBody.class, Response.class})
53+
public class OrganisationClientTest {
54+
55+
private GitHubClient github;
56+
57+
private OrganisationClient organisationClient;
58+
59+
private Json json;
60+
61+
private static String getFixture(String resource) throws IOException {
62+
return Resources.toString(getResource(OrganisationClientTest.class, resource), defaultCharset());
63+
}
64+
65+
@Before
66+
public void setUp() {
67+
github = mock(GitHubClient.class);
68+
organisationClient = new OrganisationClient(github, "github");
69+
json = Json.create();
70+
when(github.json()).thenReturn(json);
71+
}
72+
73+
@Test
74+
public void getTeam() throws Exception {
75+
final CompletableFuture<Team> fixture =
76+
completedFuture(json.fromJson(getFixture("team_get.json"), Team.class));
77+
when(github.request("/orgs/github/teams/justice-league", Team.class)).thenReturn(fixture);
78+
final Team team = organisationClient.getTeam("justice-league").get();
79+
assertThat(team.id(), is(1));
80+
assertThat(team.name(), is("Justice League"));
81+
}
82+
83+
@Test
84+
public void listTeams() throws Exception {
85+
final CompletableFuture<List<Team>> fixture =
86+
completedFuture(json.fromJson(getFixture("teams_list.json"), LIST_TEAMS));
87+
when(github.request("/orgs/github/teams", LIST_TEAMS)).thenReturn(fixture);
88+
final List<Team> teams = organisationClient.listTeams().get();
89+
assertThat(teams.get(0).slug(), is("justice-league"));
90+
assertThat(teams.get(1).slug(), is("x-men"));
91+
assertThat(teams.size(), is(2));
92+
}
93+
94+
@Test
95+
public void deleteTeam() throws Exception {
96+
final CompletableFuture<Response> response = completedFuture(mock(Response.class));
97+
final ArgumentCaptor<String> capture = ArgumentCaptor.forClass(String.class);
98+
when(github.delete(capture.capture())).thenReturn(response);
99+
100+
CompletableFuture<Void> deleteResponse = organisationClient.deleteTeam("justice-league");
101+
deleteResponse.get();
102+
assertThat(capture.getValue(), is("/orgs/github/teams/justice-league"));
103+
}
104+
105+
@Test
106+
public void createTeam() throws Exception {
107+
final TeamCreate teamCreateRequest =
108+
json.fromJson(
109+
getFixture("teams_request.json"),
110+
TeamCreate.class);
111+
112+
final CompletableFuture<Team> fixtureResponse = completedFuture(json.fromJson(
113+
getFixture("team_get.json"),
114+
Team.class));
115+
when(github.post(any(), any(), eq(Team.class))).thenReturn(fixtureResponse);
116+
final CompletableFuture<Team> actualResponse = organisationClient.createTeam(teamCreateRequest);
117+
118+
assertThat(actualResponse.get().name(), is("Justice League"));
119+
}
120+
121+
@Test
122+
public void updateTeam() throws Exception {
123+
final TeamCreate teamCreateRequest =
124+
json.fromJson(
125+
getFixture("teams_patch.json"),
126+
TeamCreate.class);
127+
128+
final CompletableFuture<Team> fixtureResponse = completedFuture(json.fromJson(
129+
getFixture("teams_patch_response.json"),
130+
Team.class));
131+
when(github.patch(any(), any(), eq(Team.class))).thenReturn(fixtureResponse);
132+
final CompletableFuture<Team> actualResponse = organisationClient.updateTeam(teamCreateRequest, "justice-league");
133+
134+
assertThat(actualResponse.get().name(), is("Justice League2"));
135+
}
136+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"id": 1,
3+
"node_id": "MDQ6VGVhbTE=",
4+
"url": "https://api.github.com/teams/1",
5+
"html_url": "https://github.com/orgs/github/teams/justice-league",
6+
"name": "Justice League",
7+
"slug": "justice-league",
8+
"description": "A great team.",
9+
"privacy": "closed",
10+
"notification_setting": "notifications_enabled",
11+
"permission": "admin",
12+
"members_url": "https://api.github.com/teams/1/members{/member}",
13+
"repositories_url": "https://api.github.com/teams/1/repos",
14+
"parent": null,
15+
"members_count": 3,
16+
"repos_count": 10,
17+
"created_at": "2017-07-14T16:53:42Z",
18+
"updated_at": "2017-08-17T12:37:15Z",
19+
"organization": {
20+
"login": "github",
21+
"id": 1,
22+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE=",
23+
"url": "https://api.github.com/orgs/github",
24+
"repos_url": "https://api.github.com/orgs/github/repos",
25+
"events_url": "https://api.github.com/orgs/github/events",
26+
"hooks_url": "https://api.github.com/orgs/github/hooks",
27+
"issues_url": "https://api.github.com/orgs/github/issues",
28+
"members_url": "https://api.github.com/orgs/github/members{/member}",
29+
"public_members_url": "https://api.github.com/orgs/github/public_members{/member}",
30+
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
31+
"description": "A great organization",
32+
"name": "github",
33+
"company": "GitHub",
34+
"blog": "https://github.com/blog",
35+
"location": "San Francisco",
36+
"email": "[email protected]",
37+
"is_verified": true,
38+
"has_organization_projects": true,
39+
"has_repository_projects": true,
40+
"public_repos": 2,
41+
"public_gists": 1,
42+
"followers": 20,
43+
"following": 0,
44+
"html_url": "https://github.com/octocat",
45+
"created_at": "2008-01-14T04:33:35Z",
46+
"updated_at": "2017-08-17T12:37:15Z",
47+
"type": "Organization"
48+
}
49+
}

0 commit comments

Comments
 (0)