/* * Copyright 2015-2018 the original author or authors. * * 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 io.spring.issuebot.github; import java.util.Date; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.RequestMatcher; import org.springframework.test.web.client.response.DefaultResponseCreator; import org.springframework.util.Base64Utils; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; /** * Tests for {@link GitHubTemplate}. * * @author Andy Wilkinson */ public class GitHubTemplateTests { private final RestTemplate rest = GitHubTemplate.createDefaultRestTemplate("username", "password"); private final MockRestServiceServer server = MockRestServiceServer .createServer(this.rest); private GitHubTemplate gitHub = new GitHubTemplate(this.rest, new RegexLinkParser()); @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void noIssues() { this.server.expect(requestTo("https://api.github.com/repos/org/repo/issues")) .andExpect(method(HttpMethod.GET)).andExpect(basicAuth()) .andRespond(withSuccess("[]", MediaType.APPLICATION_JSON)); Page issues = this.gitHub.getIssues("org", "repo"); assertThat(issues.getContent()).isEmpty(); assertThat(issues.next()).isNull(); } @Test public void singlePageOfIssues() { this.server.expect(requestTo("https://api.github.com/repos/org/repo/issues")) .andExpect(method(HttpMethod.GET)).andExpect(basicAuth()) .andRespond(withResource("issues-page-one.json")); Page issues = this.gitHub.getIssues("org", "repo"); assertThat(issues.getContent()).hasSize(15); assertThat(issues.next()).isNull(); } @Test public void multiplePagesOfIssues() { HttpHeaders headers = new HttpHeaders(); headers.set("Link", "; rel=\"next\""); this.server.expect(requestTo("https://api.github.com/repos/org/repo/issues")) .andExpect(method(HttpMethod.GET)).andExpect(basicAuth()) .andRespond(withResource("issues-page-one.json", "Link:; rel=\"next\"")); this.server.expect(requestTo("/page-two")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()).andRespond(withResource("issues-page-two.json")); Page pageOne = this.gitHub.getIssues("org", "repo"); assertThat(pageOne.getContent()).hasSize(15); Page pageTwo = pageOne.next(); assertThat(pageTwo).isNotNull(); assertThat(pageTwo.getContent()).hasSize(15); } @Test public void rateLimited() { long reset = System.currentTimeMillis(); HttpHeaders headers = new HttpHeaders(); headers.set("X-RateLimit-Remaining", "0"); headers.set("X-RateLimit-Reset", Long.toString(reset / 1000)); this.server.expect(requestTo("https://api.github.com/repos/org/repo/issues")) .andExpect(method(HttpMethod.GET)).andExpect(basicAuth()) .andRespond(withStatus(HttpStatus.FORBIDDEN).headers(headers)); this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage(equalToIgnoringCase( "Rate limit exceeded. Limit will reset at " + new Date(reset))); this.gitHub.getIssues("org", "repo"); } @Test public void noComments() { this.server.expect(requestTo("/commentsUrl")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()) .andRespond(withSuccess("[]", MediaType.APPLICATION_JSON)); Page comments = this.gitHub.getComments( new Issue(null, "commentsUrl", null, null, null, null, null, null)); assertThat(comments.getContent()).isEmpty(); assertThat(comments.next()).isNull(); } @Test public void singlePageOfComments() { this.server.expect(requestTo("/commentsUrl")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()) .andRespond(withResource("comments-page-one.json")); Page comments = this.gitHub.getComments( new Issue(null, "commentsUrl", null, null, null, null, null, null)); assertThat(comments.getContent()).hasSize(17); assertThat(comments.next()).isNull(); } @Test public void multiplePagesOfComments() { HttpHeaders headers = new HttpHeaders(); headers.set("Link", "; rel=\"next\""); this.server.expect(requestTo("/commentsUrl")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()).andRespond(withResource("comments-page-one.json", "Link:; rel=\"next\"")); this.server.expect(requestTo("/page-two")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()) .andRespond(withResource("comments-page-two.json")); Page pageOne = this.gitHub.getComments( new Issue(null, "commentsUrl", null, null, null, null, null, null)); assertThat(pageOne.getContent()).hasSize(17); Page pageTwo = pageOne.next(); assertThat(pageTwo).isNotNull(); assertThat(pageTwo.getContent()).hasSize(3); } @Test public void addLabelToIssue() { this.server.expect(requestTo("labelsUrl")).andExpect(method(HttpMethod.POST)) .andExpect(basicAuth()).andExpect(content().string("[\"test\"]")) .andRespond( withSuccess("[{\"name\":\"test\"}]", MediaType.APPLICATION_JSON)); Issue issue = new Issue("issueUrl", null, null, "labelsUrl{/name}", null, null, null, null); Issue modifiedIssue = this.gitHub.addLabel(issue, "test"); assertThat(modifiedIssue.getLabels()).hasSize(1); } @Test public void removeLabelFromIssue() { this.server.expect(requestTo("labels/test")).andExpect(method(HttpMethod.DELETE)) .andExpect(basicAuth()) .andRespond(withSuccess("[]", MediaType.APPLICATION_JSON)); Issue issue = new Issue(null, null, null, "labels{/name}", null, null, null, null); Issue modifiedIssue = this.gitHub.removeLabel(issue, "test"); assertThat(modifiedIssue.getLabels()).isEmpty(); } @Test public void removeLabelWithNameThatRequiresEncodingFromIssue() { this.server.expect(requestTo("labels/status:%20foo")) .andExpect(method(HttpMethod.DELETE)).andExpect(basicAuth()) .andRespond(withSuccess("[]", MediaType.APPLICATION_JSON)); Issue issue = new Issue(null, null, null, "labels{/name}", null, null, null, null); Issue modifiedIssue = this.gitHub.removeLabel(issue, "status: foo"); assertThat(modifiedIssue.getLabels()).isEmpty(); } @Test public void addCommentToIssue() { this.server.expect(requestTo("/commentsUrl")).andExpect(method(HttpMethod.POST)) .andExpect(basicAuth()) .andExpect(content().string("{\"body\":\"A test comment\"}")) .andRespond(withResource("new-comment.json")); Issue issue = new Issue(null, "commentsUrl", null, null, null, null, null, null); Comment comment = this.gitHub.addComment(issue, "A test comment"); assertThat(comment).isNotNull(); } @Test public void singlePageOfEvents() { this.server.expect(requestTo("/eventsUrl")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()).andRespond(withResource("events-page-one.json")); Page events = this.gitHub.getEvents( new Issue(null, null, "eventsUrl", null, null, null, null, null)); assertThat(events.getContent()).hasSize(12); assertThat(events.next()).isNull(); } @Test public void multiplePagesOfEvents() { HttpHeaders headers = new HttpHeaders(); headers.set("Link", "; rel=\"next\""); this.server.expect(requestTo("/eventsUrl")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()).andRespond(withResource("events-page-one.json", "Link:; rel=\"next\"")); this.server.expect(requestTo("/page-two")).andExpect(method(HttpMethod.GET)) .andExpect(basicAuth()).andRespond(withResource("events-page-two.json")); Page pageOne = this.gitHub.getEvents( new Issue(null, null, "eventsUrl", null, null, null, null, null)); assertThat(pageOne.getContent()).hasSize(12); Page pageTwo = pageOne.next(); assertThat(pageTwo).isNotNull(); assertThat(pageTwo.getContent()).hasSize(4); } @Test public void closeIssue() { this.server.expect(requestTo("issueUrl")).andExpect(method(HttpMethod.PATCH)) .andExpect(basicAuth()) .andExpect(content().string("{\"state\":\"closed\"}")) .andRespond(withSuccess("{\"url\":\"updatedIssueUrl\"}", MediaType.APPLICATION_JSON)); Issue closedIssue = this.gitHub .close(new Issue("issueUrl", null, null, null, null, null, null, null)); assertThat(closedIssue.getUrl()).isEqualTo("updatedIssueUrl"); } private DefaultResponseCreator withResource(String resource, String... headers) { HttpHeaders httpHeaders = new HttpHeaders(); for (String header : headers) { String[] components = header.split(":"); httpHeaders.set(components[0], components[1]); } return withSuccess(new UrlResource(getClass().getResource(resource)), MediaType.APPLICATION_JSON).headers(httpHeaders); } private RequestMatcher basicAuth() { return header("Authorization", "Basic " + new String(Base64Utils.encode("username:password".getBytes()))); } }