Skip to content

Commit 839d205

Browse files
committed
feat: Add Issue Comment Reaction functionality
1 parent c4a4e64 commit 839d205

File tree

7 files changed

+262
-10
lines changed

7 files changed

+262
-10
lines changed

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44

55
<artifactId>github-client</artifactId>
6-
<version>0.4.1-SNAPSHOT</version>
6+
<version>0.3.7-SNAPSHOT</version>
77

88
<parent>
99
<groupId>com.spotify</groupId>
@@ -221,6 +221,12 @@
221221
<version>${junit.version}</version>
222222
<scope>test</scope>
223223
</dependency>
224+
<dependency>
225+
<groupId>org.junit.jupiter</groupId>
226+
<artifactId>junit-jupiter-params</artifactId>
227+
<version>${junit.version}</version>
228+
<scope>test</scope>
229+
</dependency>
224230

225231
<dependency>
226232
<groupId>org.mockito</groupId>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.jackson;
21+
22+
import com.fasterxml.jackson.core.JsonParser;
23+
import com.fasterxml.jackson.databind.DeserializationContext;
24+
import com.fasterxml.jackson.databind.JsonDeserializer;
25+
import com.spotify.github.v3.comment.CommentReactionContent;
26+
27+
import java.io.IOException;
28+
29+
public class CommentReactionContentDeserializer extends JsonDeserializer<CommentReactionContent> {
30+
@Override
31+
public CommentReactionContent deserialize(final JsonParser p, final DeserializationContext ctxt)
32+
throws IOException {
33+
String value = p.getText();
34+
for (CommentReactionContent content : CommentReactionContent.values()) {
35+
if (content.toString().equals(value)) {
36+
return content;
37+
}
38+
}
39+
return null;
40+
}
41+
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@
2626
import com.google.common.collect.ImmutableMap;
2727
import com.spotify.github.async.AsyncPage;
2828
import com.spotify.github.v3.comment.Comment;
29+
import com.spotify.github.v3.comment.CommentReaction;
30+
import com.spotify.github.v3.comment.CommentReactionContent;
2931
import com.spotify.github.v3.issues.Issue;
3032
import java.lang.invoke.MethodHandles;
3133
import java.util.Iterator;
3234
import java.util.concurrent.CompletableFuture;
35+
36+
import okhttp3.Response;
3337
import org.slf4j.Logger;
3438
import org.slf4j.LoggerFactory;
3539

@@ -39,6 +43,8 @@ public class IssueClient {
3943
static final String COMMENTS_URI_NUMBER_TEMPLATE = "/repos/%s/%s/issues/%s/comments";
4044
static final String COMMENTS_URI_TEMPLATE = "/repos/%s/%s/issues/comments";
4145
static final String COMMENTS_URI_ID_TEMPLATE = "/repos/%s/%s/issues/comments/%s";
46+
static final String COMMENTS_REACTION_TEMPLATE = "/repos/%s/%s/issues/comments/%s/reactions";
47+
static final String COMMENTS_REACTION_ID_TEMPLATE = "/repos/%s/%s/issues/%s/reactions/%s";
4248
static final String ISSUES_URI_ID_TEMPLATE = "/repos/%s/%s/issues/%s";
4349
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
4450

@@ -137,4 +143,23 @@ private Iterator<AsyncPage<Comment>> listComments(final String path) {
137143
public CompletableFuture<Issue> getIssue(final int id) {
138144
return github.request(String.format(ISSUES_URI_ID_TEMPLATE, owner, repo, id), Issue.class);
139145
}
146+
147+
/**
148+
* Create a reaction on a comment.
149+
*
150+
* @param commentId comment id
151+
* @param reaction reaction content
152+
* @return the Comment that was just created
153+
*/
154+
public CompletableFuture<CommentReaction> createCommentReaction(final long commentId, final CommentReactionContent reaction) {
155+
final String path = String.format(COMMENTS_REACTION_TEMPLATE, owner, repo, commentId);
156+
final String requestBody = github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString()));
157+
return github.post(path, requestBody, CommentReaction.class);
158+
}
159+
160+
public CompletableFuture<Response> deleteCommentReaction(final long issueNumber, final long reactionId) {
161+
final String path = String.format(COMMENTS_REACTION_ID_TEMPLATE, owner, repo, issueNumber, reactionId);
162+
final String requestBody = github.json().toJsonUnchecked("");
163+
return github.delete(path, requestBody);
164+
}
140165
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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.comment;
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 com.spotify.github.UpdateTracking;
26+
import com.spotify.github.v3.User;
27+
import org.immutables.value.Value;
28+
29+
@Value.Immutable
30+
@GithubStyle
31+
@JsonSerialize(as = ImmutableCommentReaction.class)
32+
@JsonDeserialize(as = ImmutableCommentReaction.class)
33+
public interface CommentReaction extends UpdateTracking {
34+
35+
/** Reaction ID. */
36+
long id();
37+
38+
/** Reaction user. */
39+
User user();
40+
41+
/** Reaction content. */
42+
CommentReactionContent content();
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.comment;
21+
22+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
23+
import com.spotify.github.jackson.CommentReactionContentDeserializer;
24+
25+
@JsonDeserialize(using = CommentReactionContentDeserializer.class)
26+
public enum CommentReactionContent {
27+
THUMBS_UP("+1"), // 👍
28+
THUMBS_DOWN("-1"), // 👎
29+
LAUGH("laugh"), // 😄
30+
HOORAY("hooray"), // 🎉
31+
CONFUSED("confused"), // 😕
32+
HEART("heart"), // ❤️
33+
ROCKET("rocket"), // 🚀
34+
EYES("eyes"); // 👀
35+
36+
private final String reaction;
37+
38+
CommentReactionContent(final String reaction) {
39+
this.reaction = reaction;
40+
}
41+
42+
@Override
43+
public String toString() {
44+
return reaction;
45+
}
46+
}

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

Lines changed: 60 additions & 9 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.
@@ -22,8 +22,7 @@
2222

2323
import static com.google.common.io.Resources.getResource;
2424
import static com.spotify.github.FixtureHelper.loadFixture;
25-
import static com.spotify.github.v3.clients.IssueClient.COMMENTS_URI_NUMBER_TEMPLATE;
26-
import static com.spotify.github.v3.clients.IssueClient.ISSUES_URI_ID_TEMPLATE;
25+
import static com.spotify.github.v3.clients.IssueClient.*;
2726
import static com.spotify.github.v3.clients.MockHelper.createMockResponse;
2827
import static java.lang.String.format;
2928
import static java.nio.charset.Charset.defaultCharset;
@@ -37,15 +36,19 @@
3736
import static org.junit.jupiter.api.Assertions.assertThrows;
3837
import static org.mockito.ArgumentMatchers.anyString;
3938
import static org.mockito.ArgumentMatchers.eq;
40-
import static org.mockito.Mockito.mock;
41-
import static org.mockito.Mockito.when;
39+
import static org.mockito.Mockito.*;
4240

41+
import com.google.common.collect.ImmutableMap;
4342
import com.google.common.collect.Lists;
4443
import com.google.common.io.Resources;
4544
import com.spotify.github.async.Async;
4645
import com.spotify.github.async.AsyncPage;
4746
import com.spotify.github.jackson.Json;
47+
import com.spotify.github.v3.ImmutableUser;
4848
import com.spotify.github.v3.comment.Comment;
49+
import com.spotify.github.v3.comment.CommentReaction;
50+
import com.spotify.github.v3.comment.CommentReactionContent;
51+
import com.spotify.github.v3.comment.ImmutableCommentReaction;
4952
import com.spotify.github.v3.exceptions.RequestNotOkException;
5053
import com.spotify.github.v3.issues.Issue;
5154
import java.io.IOException;
@@ -56,14 +59,15 @@
5659
import okhttp3.Response;
5760
import org.junit.jupiter.api.BeforeEach;
5861
import org.junit.jupiter.api.Test;
62+
import org.junit.jupiter.params.ParameterizedTest;
63+
import org.junit.jupiter.params.provider.EnumSource;
5964

6065
public class IssueClientTest {
6166

6267
private GitHubClient github;
6368
private IssueClient issueClient;
6469
private Json json;
6570

66-
6771
@BeforeEach
6872
public void setUp() {
6973
json = Json.create();
@@ -94,7 +98,8 @@ public void testCommentPaginationSpliterator() throws IOException {
9498
.thenReturn(completedFuture(lastPageResponse));
9599

96100
final Iterable<AsyncPage<Comment>> pageIterator = () -> issueClient.listComments(123);
97-
final List<Comment> listComments = Async.streamFromPaginatingIterable(pageIterator).collect(toList());
101+
final List<Comment> listComments =
102+
Async.streamFromPaginatingIterable(pageIterator).collect(toList());
98103

99104
assertThat(listComments.size(), is(30));
100105
assertThat(listComments.get(0).id(), is(1345268));
@@ -161,10 +166,56 @@ public void testGetIssue() throws IOException {
161166
assertThat(issue.labels().get(0).name(), is("bug"));
162167
}
163168

169+
@ParameterizedTest
170+
@EnumSource(CommentReactionContent.class)
171+
public void testCreateIssueCommentReaction(CommentReactionContent reaction) {
172+
long commentId = 22369886;
173+
final CompletableFuture<CommentReaction> reactionResponse =
174+
completedFuture(
175+
ImmutableCommentReaction.builder()
176+
.id(42L)
177+
.content(reaction)
178+
.user(ImmutableUser.builder().login("octocat").build())
179+
.build());
180+
final String path = format(COMMENTS_REACTION_TEMPLATE, "someowner", "somerepo", commentId);
181+
final String requestBody =
182+
github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString()));
183+
when(github.post(eq(path), eq(requestBody), eq(CommentReaction.class)))
184+
.thenReturn(reactionResponse);
185+
186+
final var commentReaction =
187+
issueClient.createCommentReaction(commentId, reaction).join();
188+
189+
assertThat(commentReaction.id(), is(42L));
190+
assertNotNull(commentReaction.user());
191+
assertThat(commentReaction.user().login(), is("octocat"));
192+
assertThat(commentReaction.content().toString(), is(reaction.toString()));
193+
verify(github, times(1)).post(eq(path), eq(requestBody), eq(CommentReaction.class));
194+
}
195+
196+
@Test
197+
public void testDeleteIssueCommentReaction() {
198+
long issueNumber = 42;
199+
long reactionId = 385825;
200+
final String path =
201+
format(COMMENTS_REACTION_ID_TEMPLATE, "someowner", "somerepo", issueNumber, reactionId);
202+
final String requestBody = github.json().toJsonUnchecked("");
203+
Response mockResponse = mock(Response.class);
204+
when(mockResponse.code()).thenReturn(204);
205+
when(github.delete(eq(path), eq(requestBody))).thenReturn(completedFuture(mockResponse));
206+
207+
final var response = issueClient.deleteCommentReaction(issueNumber, reactionId).join();
208+
209+
assertThat(response.code(), is(204));
210+
assertThat(response, is(mockResponse));
211+
verify(github, times(1)).delete(eq(path), eq(requestBody));
212+
}
213+
164214
@Test
165215
public void testGetIssueNoIssue() {
166216
final String path = format(ISSUES_URI_ID_TEMPLATE, "someowner", "somerepo", 2);
167-
when(github.request(eq(path), eq(Issue.class))).thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>())));
217+
when(github.request(eq(path), eq(Issue.class)))
218+
.thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>())));
168219

169220
assertThrows(CompletionException.class, () -> issueClient.getIssue(2).join());
170221
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.comment;
21+
22+
import static org.junit.jupiter.api.Assertions.*;
23+
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.EnumSource;
27+
28+
class CommentReactionContentTest {
29+
@ParameterizedTest
30+
@EnumSource(CommentReactionContent.class)
31+
public void testDeserialize(CommentReactionContent reaction) throws Exception {
32+
ObjectMapper mapper = new ObjectMapper();
33+
34+
String json = "\"" + reaction.toString() + "\"";
35+
36+
CommentReactionContent content = mapper.readValue(json, CommentReactionContent.class);
37+
38+
assertEquals(reaction, content);
39+
}
40+
}

0 commit comments

Comments
 (0)