Skip to content

Commit 7cd4242

Browse files
Joonpark13Abhi347
andauthored
Add collaborator methods (#97)
* Add collaborator methods * Add test coverage * revert syntax change * Update src/main/java/com/spotify/github/v3/clients/GitHubClient.java Co-authored-by: Abhishek Jain <[email protected]> Co-authored-by: Abhishek Jain <[email protected]>
1 parent f34e572 commit 7cd4242

File tree

7 files changed

+407
-6
lines changed

7 files changed

+407
-6
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,20 @@ CompletableFuture<Response> put(final String path, final String data) {
513513
return call(request);
514514
}
515515

516+
/**
517+
* Make a HTTP PUT request for the given path with provided JSON body.
518+
*
519+
* @param path relative to the Github base url
520+
* @param data request body as stringified JSON
521+
* @param clazz class to cast response as
522+
* @return response body as String
523+
*/
524+
<T> CompletableFuture<T> put(final String path, final String data, final Class<T> clazz) {
525+
return put(path, data)
526+
.thenApply(
527+
response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz));
528+
}
529+
516530
/**
517531
* Make an http PATCH request for the given path with provided JSON body.
518532
*

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.spotify.github.v3.repos.FolderContent;
4646
import com.spotify.github.v3.repos.Languages;
4747
import com.spotify.github.v3.repos.Repository;
48+
import com.spotify.github.v3.repos.RepositoryInvitation;
4849
import com.spotify.github.v3.repos.Status;
4950
import com.spotify.github.v3.repos.requests.AuthenticatedUserRepositoriesFilter;
5051
import com.spotify.github.v3.repos.requests.RepositoryCreateStatus;
@@ -86,7 +87,7 @@ public class RepositoryClient {
8687
private static final String FORK_TEMPLATE = "/repos/%s/%s/forks";
8788
private static final String LIST_REPOSITORY_TEMPLATE = "/orgs/%s/repos";
8889
private static final String LIST_REPOSITORIES_FOR_AUTHENTICATED_USER = "/user/repos";
89-
private static final String IS_USER_COLLABORATOR_OF_REPO = "/repos/%s/%s/collaborators/%s";
90+
private static final String REPOSITORY_COLLABORATOR = "/repos/%s/%s/collaborators/%s";
9091
private final String owner;
9192
private final String repo;
9293
private final GitHubClient github;
@@ -182,10 +183,20 @@ public Iterator<AsyncPage<Repository>> listAuthenticatedUserRepositories(
182183
* @return boolean indicating if user is collaborator
183184
*/
184185
public CompletableFuture<Boolean> isCollaborator(final String user) {
185-
final String path = String.format(IS_USER_COLLABORATOR_OF_REPO, owner, repo, user);
186+
final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user);
186187
return github.request(path).thenApply(response -> response.code() == NO_CONTENT);
187188
}
188189

190+
public CompletableFuture<RepositoryInvitation> addCollaborator(final String user) {
191+
final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user);
192+
return github.put(path, "", RepositoryInvitation.class);
193+
}
194+
195+
public CompletableFuture<Void> removeCollaborator(final String user) {
196+
final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user);
197+
return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER);
198+
}
199+
189200
/**
190201
* Create a webhook.
191202
*
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*-
2+
* -\-\-
3+
* github-api
4+
* --
5+
* Copyright (C) 2016 - 2022 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.repos;
22+
23+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
24+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
25+
import com.spotify.github.GithubStyle;
26+
import com.spotify.github.v3.User;
27+
import java.net.URI;
28+
import java.time.ZonedDateTime;
29+
import java.util.Optional;
30+
import javax.annotation.Nullable;
31+
import org.immutables.value.Value;
32+
33+
/** Collaborator Invitation resource */
34+
@Value.Immutable
35+
@GithubStyle
36+
@JsonSerialize(as = ImmutableRepositoryInvitation.class)
37+
@JsonDeserialize(as = ImmutableRepositoryInvitation.class)
38+
public interface RepositoryInvitation {
39+
40+
/** Unique identifier of the repository invitation */
41+
Integer id();
42+
43+
/** Node ID */
44+
String nodeId();
45+
46+
/** The repository that the invitee is being invited to */
47+
Repository repository();
48+
49+
/** The user that is receiving the invite */
50+
@Nullable
51+
User invitee();
52+
53+
/** The user that sent the invite */
54+
@Nullable
55+
User inviter();
56+
57+
/** The permission associated with the invitation */
58+
String permissions();
59+
60+
/** Date when invite was created */
61+
ZonedDateTime createdAt();
62+
63+
/** Whether or not the invitation has expired */
64+
@Nullable
65+
Optional<Boolean> expired();
66+
67+
/** API URL */
68+
URI url();
69+
70+
/** HTML URL */
71+
URI htmlUrl();
72+
}

src/test/java/com/spotify/github/v3/clients/GitHubClientTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,22 @@
2020

2121
package com.spotify.github.v3.clients;
2222

23+
import static com.google.common.io.Resources.getResource;
24+
import static java.nio.charset.Charset.defaultCharset;
2325
import static org.hamcrest.CoreMatchers.containsString;
2426
import static org.hamcrest.MatcherAssert.assertThat;
2527
import static org.hamcrest.core.Is.is;
2628
import static org.junit.Assert.fail;
2729
import static org.mockito.ArgumentMatchers.any;
2830
import static org.mockito.Mockito.*;
2931

32+
import com.google.common.io.Resources;
3033
import com.spotify.github.Tracer;
3134
import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException;
3235
import com.spotify.github.v3.exceptions.RequestNotOkException;
3336
import com.spotify.github.v3.repos.CommitItem;
37+
import com.spotify.github.v3.repos.RepositoryInvitation;
38+
import java.io.IOException;
3439
import java.net.URI;
3540
import java.util.Optional;
3641
import java.util.concurrent.CompletableFuture;
@@ -53,6 +58,10 @@ public class GitHubClientTest {
5358
private OkHttpClient client;
5459
private Tracer tracer = mock(Tracer.class);
5560

61+
private static String getFixture(String resource) throws IOException {
62+
return Resources.toString(getResource(GitHubClientTest.class, resource), defaultCharset());
63+
}
64+
5665
@Before
5766
public void setUp() {
5867
client = mock(OkHttpClient.class);
@@ -138,4 +147,34 @@ public void testRequestNotOkException() throws Throwable {
138147
assertThat(e1.getRawMessage(), containsString("Merge Conflict"));
139148
}
140149
}
150+
151+
@Test
152+
public void testPutConvertsToClass() throws Throwable {
153+
final Call call = mock(Call.class);
154+
final ArgumentCaptor<Callback> callbackCapture = ArgumentCaptor.forClass(Callback.class);
155+
doNothing().when(call).enqueue(callbackCapture.capture());
156+
157+
final ArgumentCaptor<Request> requestCapture = ArgumentCaptor.forClass(Request.class);
158+
when(client.newCall(requestCapture.capture())).thenReturn(call);
159+
160+
final Response response =
161+
new okhttp3.Response.Builder()
162+
.code(200)
163+
.body(
164+
ResponseBody.create(
165+
MediaType.get("application/json"), getFixture("repository_invitation.json")))
166+
.message("")
167+
.protocol(Protocol.HTTP_1_1)
168+
.request(new Request.Builder().url("http://localhost/").build())
169+
.build();
170+
171+
CompletableFuture<RepositoryInvitation> future = github.put("collaborators/", "",
172+
RepositoryInvitation.class);
173+
callbackCapture.getValue().onResponse(call, response);
174+
175+
RepositoryInvitation invitation = future.get();
176+
assertThat(requestCapture.getValue().method(), is("PUT"));
177+
assertThat(requestCapture.getValue().url().toString(), is("http://bogus/collaborators/"));
178+
assertThat(invitation.id(), is(1));
179+
}
141180
}

src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,8 +23,8 @@
2323
import static com.google.common.io.Resources.getResource;
2424
import static com.spotify.github.FixtureHelper.loadFixture;
2525
import static com.spotify.github.v3.UserTest.assertUser;
26-
import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE;
2726
import static com.spotify.github.v3.clients.GitHubClient.LIST_BRANCHES;
27+
import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE;
2828
import static com.spotify.github.v3.clients.GitHubClient.LIST_FOLDERCONTENT_TYPE_REFERENCE;
2929
import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_TYPE_REFERENCE;
3030
import static com.spotify.github.v3.clients.GitHubClient.LIST_REPOSITORY;
@@ -33,6 +33,7 @@
3333
import static java.lang.String.format;
3434
import static java.nio.charset.Charset.defaultCharset;
3535
import static java.util.concurrent.CompletableFuture.completedFuture;
36+
import static java.util.stream.StreamSupport.stream;
3637
import static org.hamcrest.MatcherAssert.assertThat;
3738
import static org.hamcrest.core.Is.is;
3839
import static org.junit.Assert.assertFalse;
@@ -41,7 +42,6 @@
4142
import static org.mockito.ArgumentMatchers.eq;
4243
import static org.mockito.Mockito.mock;
4344
import static org.mockito.Mockito.when;
44-
import static java.util.stream.StreamSupport.stream;
4545

4646
import com.google.common.collect.ImmutableMap;
4747
import com.google.common.collect.Lists;
@@ -59,6 +59,7 @@
5959
import com.spotify.github.v3.repos.Content;
6060
import com.spotify.github.v3.repos.FolderContent;
6161
import com.spotify.github.v3.repos.Repository;
62+
import com.spotify.github.v3.repos.RepositoryInvitation;
6263
import com.spotify.github.v3.repos.RepositoryTest;
6364
import com.spotify.github.v3.repos.Status;
6465
import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter;
@@ -76,6 +77,7 @@
7677
import org.junit.Before;
7778
import org.junit.Test;
7879
import org.junit.runner.RunWith;
80+
import org.mockito.ArgumentCaptor;
7981
import org.powermock.core.classloader.annotations.PrepareForTest;
8082
import org.powermock.modules.junit4.PowerMockRunner;
8183

@@ -160,6 +162,35 @@ public void isNotCollaborator() throws Exception {
160162
assertFalse(isCollaborator);
161163
}
162164

165+
@Test
166+
public void addCollaborator() throws Exception {
167+
final CompletableFuture<RepositoryInvitation> fixture =
168+
completedFuture(json.fromJson(getFixture("repository_invitation.json"), RepositoryInvitation.class));
169+
when(github.put("/repos/someowner/somerepo/collaborators/user", "", RepositoryInvitation.class)).thenReturn(fixture);
170+
171+
final RepositoryInvitation repoInvite = repoClient.addCollaborator("user").get();
172+
173+
assertThat(repoInvite.id(), is(1));
174+
assertThat(repoInvite.nodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"));
175+
assertThat(repoInvite.repository().id(), is(1296269));
176+
assertUser(repoInvite.repository().owner());
177+
assertUser(repoInvite.invitee());
178+
assertUser(repoInvite.inviter());
179+
assertThat(repoInvite.permissions(), is("write"));
180+
}
181+
182+
@Test
183+
public void removeCollaborator() throws Exception {
184+
CompletableFuture<Response> response = completedFuture(mock(Response.class));
185+
final ArgumentCaptor<String> capture = ArgumentCaptor.forClass(String.class);
186+
when(github.delete(capture.capture())).thenReturn(response);
187+
188+
CompletableFuture<Void> deleteResponse = repoClient.removeCollaborator("user");
189+
deleteResponse.get();
190+
191+
assertThat(capture.getValue(), is("/repos/someowner/somerepo/collaborators/user"));
192+
}
193+
163194
@Test
164195
public void listCommits() throws Exception {
165196
final CompletableFuture<List<CommitItem>> fixture =

0 commit comments

Comments
 (0)