Skip to content

Commit 10c44f7

Browse files
authored
Add repository collaborator; handle 204 response, accept permission (#106)
1 parent 6a8026d commit 10c44f7

File tree

3 files changed

+88
-8
lines changed

3 files changed

+88
-8
lines changed

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,41 @@ public CompletableFuture<Boolean> isCollaborator(final String user) {
187187
return github.request(path).thenApply(response -> response.code() == NO_CONTENT);
188188
}
189189

190-
public CompletableFuture<RepositoryInvitation> addCollaborator(final String user) {
190+
/**
191+
* Add a collaborator to the repo.
192+
*
193+
* @param user the GitHub username to add
194+
* @param permission the permission level for the user; one of RepositoryPermission, or a custom
195+
* role
196+
* @return
197+
*/
198+
public CompletableFuture<Optional<RepositoryInvitation>> addCollaborator(final String user,
199+
final String permission) {
191200
final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user);
192-
return github.put(path, "", RepositoryInvitation.class);
201+
final String data = github.json().toJsonUnchecked(Map.of("permission", permission));
202+
return github
203+
.put(path, data)
204+
.thenApply(
205+
response -> {
206+
// Non-successful statuses result in an RequestNotOkException exception and this code
207+
// not called.
208+
if (response.code() == NO_CONTENT) {
209+
/*
210+
GitHub returns a 204 when:
211+
- an existing collaborator is added as a collaborator
212+
- an organization member is added as an individual collaborator
213+
- an existing team member (whose team is also a repository collaborator) is
214+
added as a collaborator
215+
*/
216+
return Optional.empty();
217+
}
218+
final RepositoryInvitation invitation =
219+
github
220+
.json()
221+
.fromJsonUnchecked(
222+
GitHubClient.responseBodyUnchecked(response), RepositoryInvitation.class);
223+
return Optional.of(invitation);
224+
});
193225
}
194226

195227
public CompletableFuture<Void> removeCollaborator(final String user) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*-
2+
* -\-\-
3+
* github-client
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+
/** Helpful constants for Repository permissions. */
24+
public class RepositoryPermission {
25+
26+
public static final String PULL = "pull";
27+
public static final String PUSH = "push";
28+
public static final String ADMIN = "admin";
29+
public static final String MAINTAIN = "maintain";
30+
public static final String TRIAGE = "triage";
31+
32+
private RepositoryPermission() {}
33+
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,13 @@
3939
import static org.junit.Assert.assertFalse;
4040
import static org.junit.Assert.assertTrue;
4141
import static org.mockito.ArgumentMatchers.any;
42-
import static org.mockito.ArgumentMatchers.contains;
4342
import static org.mockito.ArgumentMatchers.eq;
4443
import static org.mockito.Mockito.mock;
4544
import static org.mockito.Mockito.when;
4645

4746
import com.google.common.collect.ImmutableMap;
4847
import com.google.common.collect.Lists;
4948
import com.google.common.io.Resources;
50-
import com.google.common.net.HttpHeaders;
5149
import com.spotify.github.async.AsyncPage;
5250
import com.spotify.github.jackson.Json;
5351
import com.spotify.github.v3.comment.Comment;
@@ -61,6 +59,7 @@
6159
import com.spotify.github.v3.repos.FolderContent;
6260
import com.spotify.github.v3.repos.Repository;
6361
import com.spotify.github.v3.repos.RepositoryInvitation;
62+
import com.spotify.github.v3.repos.RepositoryPermission;
6463
import com.spotify.github.v3.repos.RepositoryTest;
6564
import com.spotify.github.v3.repos.Status;
6665
import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter;
@@ -165,12 +164,15 @@ public void isNotCollaborator() throws Exception {
165164

166165
@Test
167166
public void addCollaborator() throws Exception {
168-
final CompletableFuture<RepositoryInvitation> fixture =
169-
completedFuture(json.fromJson(getFixture("repository_invitation.json"), RepositoryInvitation.class));
170-
when(github.put("/repos/someowner/somerepo/collaborators/user", "", RepositoryInvitation.class)).thenReturn(fixture);
167+
final Response response = createMockResponse("", getFixture("repository_invitation.json"));
168+
when(github.put("/repos/someowner/somerepo/collaborators/user", "{\"permission\":\"pull\"}")).thenReturn(
169+
completedFuture(response));
171170

172-
final RepositoryInvitation repoInvite = repoClient.addCollaborator("user").get();
171+
final Optional<RepositoryInvitation> maybeInvite = repoClient.addCollaborator("user",
172+
RepositoryPermission.PULL).get();
173173

174+
assertTrue(maybeInvite.isPresent());
175+
final RepositoryInvitation repoInvite = maybeInvite.get();
174176
assertThat(repoInvite.id(), is(1));
175177
assertThat(repoInvite.nodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5"));
176178
assertThat(repoInvite.repository().id(), is(1296269));
@@ -180,6 +182,19 @@ public void addCollaborator() throws Exception {
180182
assertThat(repoInvite.permissions(), is("write"));
181183
}
182184

185+
@Test
186+
public void addCollaboratorUserExists() throws Exception {
187+
final Response response = mock(Response.class);
188+
when(response.code()).thenReturn(204);
189+
when(github.put("/repos/someowner/somerepo/collaborators/user", "{\"permission\":\"pull\"}")).thenReturn(
190+
completedFuture(response));
191+
192+
final Optional<RepositoryInvitation> maybeInvite = repoClient.addCollaborator("user",
193+
RepositoryPermission.PULL).get();
194+
195+
assertTrue(maybeInvite.isEmpty());
196+
}
197+
183198
@Test
184199
public void removeCollaborator() throws Exception {
185200
CompletableFuture<Response> response = completedFuture(mock(Response.class));

0 commit comments

Comments
 (0)