diff --git a/github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala b/github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala index c9560617c..8cd5a8c58 100644 --- a/github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala +++ b/github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala @@ -41,6 +41,7 @@ class ApiSpec val gitData = new GitData[HttpResponse[String], Id] val pullRequests = new PullRequests[HttpResponse[String], Id] val statuses = new Statuses[HttpResponse[String], Id] + val issues = new Issues[HttpResponse[String], Id] "Auth >> NewAuth" should "return a valid token when valid credential is provided" in { val response = auth.newAuth( @@ -448,7 +449,7 @@ class ApiSpec headerUserAgent, validRepoOwner, validRepoName, - s"refs/$validRefSingle", + validRefSingle, validCommitSha) response should be('left) } @@ -711,7 +712,7 @@ class ApiSpec } } - "Statuses >> Create" should "return the create status if a valid sha is provided" in { + "Statuses >> Create" should "return the created status if a valid sha is provided" in { val response = statuses.create( accessToken, headerUserAgent, @@ -756,4 +757,145 @@ class ApiSpec None) response should be('left) } + + "Issues >> List" should "return the expected issues when a valid owner/repo is provided" in { + val response = + issues.list(accessToken, headerUserAgent, validRepoOwner, validRepoName) + response should be('right) + + response.toOption map { r ⇒ + r.result.nonEmpty shouldBe true + r.statusCode shouldBe okStatusCode + } + } + + it should "return an error if invalid data is provided" in { + val response = + issues.list(accessToken, headerUserAgent, validRepoOwner, invalidRepoName) + response should be('left) + } + + it should "return an error if no tokens are provided" in { + val response = + issues.list(None, headerUserAgent, validRepoOwner, validRepoName) + response should be('left) + } + + "Issues >> Create" should "return the created issue if valid data is provided" in { + val response = issues.create( + accessToken, + headerUserAgent, + validRepoOwner, + validRepoName, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('right) + + response.toOption map { r => + r.statusCode shouldBe createdStatusCode + } + } + + it should "return an error if invalid data is provided" in { + val response = issues.create( + accessToken, + headerUserAgent, + validRepoOwner, + invalidRepoName, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('left) + } + + it should "return an error if no tokens are provided" in { + val response = issues.create( + None, + headerUserAgent, + validRepoOwner, + validRepoName, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('left) + } + + "Issues >> Edit" should "return the edited issue if valid data is provided" in { + val response = issues.edit( + accessToken, + headerUserAgent, + validRepoOwner, + validRepoName, + validIssueNumber, + validIssueState, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('right) + + response.toOption map { r => + r.statusCode shouldBe okStatusCode + } + } + + it should "return an error if invalid data is provided" in { + val response = issues.edit( + accessToken, + headerUserAgent, + validRepoOwner, + invalidRepoName, + validIssueNumber, + validIssueState, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('left) + } + + it should "return an error if no tokens are provided" in { + val response = issues.edit( + None, + headerUserAgent, + validRepoOwner, + validRepoName, + validIssueNumber, + validIssueState, + validIssueTitle, + validIssueBody, + None, + List.empty, + List.empty) + response should be('left) + } + + "Issues >> Search" should "return the search result if valid data is provided" in { + val response = issues.search(accessToken, headerUserAgent, validSearchQuery, List.empty) + response should be('right) + + response.toOption map { r => + r.statusCode shouldBe okStatusCode + } + } + + it should "return an empty result if a search query matching nothing is provided" in { + val response = + issues.search(accessToken, headerUserAgent, nonExistentSearchQuery, List.empty) + response should be('right) + + response.toOption map { r ⇒ + r.result.items.isEmpty shouldBe true + r.statusCode shouldBe okStatusCode + } + } } diff --git a/github4s/jvm/src/test/scala/github4s/utils/MockGithubApiServer.scala b/github4s/jvm/src/test/scala/github4s/utils/MockGithubApiServer.scala index 3c221c106..0754151d8 100644 --- a/github4s/jvm/src/test/scala/github4s/utils/MockGithubApiServer.scala +++ b/github4s/jvm/src/test/scala/github4s/utils/MockGithubApiServer.scala @@ -20,6 +20,7 @@ import org.mockserver.model.HttpRequest._ import org.mockserver.model.HttpResponse._ import org.mockserver.model.JsonBody._ import org.mockserver.model.NottableString._ +import org.mockserver.model.Parameter trait MockGithubApiServer extends MockServerService with FakeResponses with TestUtilsJVM { @@ -596,4 +597,98 @@ trait MockGithubApiServer extends MockServerService with FakeResponses with Test .withPath(s"/repos/$validRepoOwner/$validRepoName/statuses/$invalidCommitSha") .withHeader("Authorization", tokenHeader)) .respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse)) + + //Issues >> list + mockServer + .when( + request + .withMethod("GET") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues") + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(okStatusCode).withBody(listIssuesValidResponse)) + + mockServer + .when( + request + .withMethod("GET") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues") + .withHeader(not("Authorization"))) + .respond(response.withStatusCode(unauthorizedStatusCode).withBody(unauthorizedResponse)) + + mockServer + .when( + request + .withMethod("GET") + .withPath(s"/repos/$validRepoOwner/$invalidRepoName/issues") + .withHeader(not("Authorization"))) + .respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse)) + + //Issues >> create + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues") + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(createdStatusCode).withBody(createIssueValidResponse)) + + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues") + .withHeader(not("Authorization"))) + .respond(response.withStatusCode(unauthorizedStatusCode).withBody(unauthorizedResponse)) + + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$invalidRepoName/issues") + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse)) + + //Issues >> edit + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues/$validIssueNumber") + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(okStatusCode).withBody(createIssueValidResponse)) + + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$validRepoName/issues/$validIssueNumber") + .withHeader(not("Authorization"))) + .respond(response.withStatusCode(unauthorizedStatusCode).withBody(unauthorizedResponse)) + + mockServer + .when( + request + .withMethod("POST") + .withPath(s"/repos/$validRepoOwner/$invalidRepoName/issues/$validIssueNumber") + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse)) + + //Issues >> search + mockServer + .when( + request + .withMethod("GET") + .withPath(s"/search/issues") + .withQueryStringParameters(new Parameter("q", s".*$validSearchQuery.*")) + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(okStatusCode).withBody(searchIssuesValidResponse)) + + mockServer + .when( + request + .withMethod("GET") + .withPath(s"/search/issues") + .withQueryStringParameters(new Parameter("q", s".*$nonExistentSearchQuery.*")) + .withHeader("Authorization", tokenHeader)) + .respond(response.withStatusCode(okStatusCode).withBody(searchIssuesEmptyResponse)) } diff --git a/github4s/shared/src/test/scala/github4s/unit/IssuesSpec.scala b/github4s/shared/src/test/scala/github4s/unit/IssuesSpec.scala index 8215151df..ea5d88476 100644 --- a/github4s/shared/src/test/scala/github4s/unit/IssuesSpec.scala +++ b/github4s/shared/src/test/scala/github4s/unit/IssuesSpec.scala @@ -47,7 +47,7 @@ class IssuesSpec extends BaseSpec { val httpClientMock = httpClientMockGet[SearchIssuesResult]( url = s"search/issues", response = response, - params = Map("q" -> "+repo:47deg/github4s+type:issue+in:title") + params = Map("q" -> s"+${validSearchParams.map(_.value).mkString("+")}") ) val issues = new Issues[String, Id] { diff --git a/github4s/shared/src/test/scala/github4s/utils/FakeResponses.scala b/github4s/shared/src/test/scala/github4s/utils/FakeResponses.scala index 03fab2d88..944f2e3f8 100644 --- a/github4s/shared/src/test/scala/github4s/utils/FakeResponses.scala +++ b/github4s/shared/src/test/scala/github4s/utils/FakeResponses.scala @@ -1745,4 +1745,221 @@ trait FakeResponses { } } """ + + val listIssuesValidResponse = + """ + [ + { + "url": "https://api.github.com/repos/47deg/github4s/issues/42", + "repository_url": "https://api.github.com/repos/47deg/github4s", + "labels_url": "https://api.github.com/repos/47deg/github4s/issues/42/labels{/name}", + "comments_url": "https://api.github.com/repos/47deg/github4s/issues/42/comments", + "events_url": "https://api.github.com/repos/47deg/github4s/issues/42/events", + "html_url": "https://github.com/47deg/github4s/issues/42", + "id": 187293124, + "number": 42, + "title": "Update documentation / microsite", + "user": { + "login": "jdesiloniz", + "id": 2835739, + "avatar_url": "https://avatars1.githubusercontent.com/u/2835739?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/jdesiloniz", + "html_url": "https://github.com/jdesiloniz", + "followers_url": "https://api.github.com/users/jdesiloniz/followers", + "following_url": "https://api.github.com/users/jdesiloniz/following{/other_user}", + "gists_url": "https://api.github.com/users/jdesiloniz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdesiloniz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdesiloniz/subscriptions", + "organizations_url": "https://api.github.com/users/jdesiloniz/orgs", + "repos_url": "https://api.github.com/users/jdesiloniz/repos", + "events_url": "https://api.github.com/users/jdesiloniz/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdesiloniz/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 3, + "created_at": "2016-11-04T09:57:33Z", + "updated_at": "2017-04-21T14:59:29Z", + "closed_at": null, + "body": "We should take some time to update the docs and the microsite to reflect the changes that have been included in the library, maybe splitting the docs in several sections for the JVM and JS case uses." + } + ] + """ + + val createIssueValidResponse = + """ + { + "url": "https://api.github.com/repos/47deg/github4s/issues/42", + "repository_url": "https://api.github.com/repos/47deg/github4s", + "labels_url": "https://api.github.com/repos/47deg/github4s/issues/42/labels{/name}", + "comments_url": "https://api.github.com/repos/47deg/github4s/issues/42/comments", + "events_url": "https://api.github.com/repos/47deg/github4s/issues/42/events", + "html_url": "https://github.com/47deg/github4s/issues/42", + "id": 187293124, + "number": 42, + "title": "Update documentation / microsite", + "user": { + "login": "jdesiloniz", + "id": 2835739, + "avatar_url": "https://avatars1.githubusercontent.com/u/2835739?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/jdesiloniz", + "html_url": "https://github.com/jdesiloniz", + "followers_url": "https://api.github.com/users/jdesiloniz/followers", + "following_url": "https://api.github.com/users/jdesiloniz/following{/other_user}", + "gists_url": "https://api.github.com/users/jdesiloniz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdesiloniz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdesiloniz/subscriptions", + "organizations_url": "https://api.github.com/users/jdesiloniz/orgs", + "repos_url": "https://api.github.com/users/jdesiloniz/repos", + "events_url": "https://api.github.com/users/jdesiloniz/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdesiloniz/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 3, + "created_at": "2016-11-04T09:57:33Z", + "updated_at": "2017-04-21T14:59:29Z", + "closed_at": null, + "body": "We should take some time to update the docs and the microsite to reflect the changes that have been included in the library, maybe splitting the docs in several sections for the JVM and JS case uses." + } + """ + + val searchIssuesValidResponse = + """ + { + "total_count": 1, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/47deg/github4s/issues/40", + "repository_url": "https://api.github.com/repos/47deg/github4s", + "labels_url": "https://api.github.com/repos/47deg/github4s/issues/40/labels{/name}", + "comments_url": "https://api.github.com/repos/47deg/github4s/issues/40/comments", + "events_url": "https://api.github.com/repos/47deg/github4s/issues/40/events", + "html_url": "https://github.com/47deg/github4s/issues/40", + "id": 187172114, + "number": 40, + "title": "Publish version for Scala 2.12", + "user": { + "login": "raulraja", + "id": 456796, + "avatar_url": "https://avatars0.githubusercontent.com/u/456796?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/raulraja", + "html_url": "https://github.com/raulraja", + "followers_url": "https://api.github.com/users/raulraja/followers", + "following_url": "https://api.github.com/users/raulraja/following{/other_user}", + "gists_url": "https://api.github.com/users/raulraja/gists{/gist_id}", + "starred_url": "https://api.github.com/users/raulraja/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/raulraja/subscriptions", + "organizations_url": "https://api.github.com/users/raulraja/orgs", + "repos_url": "https://api.github.com/users/raulraja/repos", + "events_url": "https://api.github.com/users/raulraja/events{/privacy}", + "received_events_url": "https://api.github.com/users/raulraja/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "jdesiloniz", + "id": 2835739, + "avatar_url": "https://avatars1.githubusercontent.com/u/2835739?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/jdesiloniz", + "html_url": "https://github.com/jdesiloniz", + "followers_url": "https://api.github.com/users/jdesiloniz/followers", + "following_url": "https://api.github.com/users/jdesiloniz/following{/other_user}", + "gists_url": "https://api.github.com/users/jdesiloniz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdesiloniz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdesiloniz/subscriptions", + "organizations_url": "https://api.github.com/users/jdesiloniz/orgs", + "repos_url": "https://api.github.com/users/jdesiloniz/repos", + "events_url": "https://api.github.com/users/jdesiloniz/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdesiloniz/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "jdesiloniz", + "id": 2835739, + "avatar_url": "https://avatars1.githubusercontent.com/u/2835739?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/jdesiloniz", + "html_url": "https://github.com/jdesiloniz", + "followers_url": "https://api.github.com/users/jdesiloniz/followers", + "following_url": "https://api.github.com/users/jdesiloniz/following{/other_user}", + "gists_url": "https://api.github.com/users/jdesiloniz/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jdesiloniz/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jdesiloniz/subscriptions", + "organizations_url": "https://api.github.com/users/jdesiloniz/orgs", + "repos_url": "https://api.github.com/users/jdesiloniz/repos", + "events_url": "https://api.github.com/users/jdesiloniz/events{/privacy}", + "received_events_url": "https://api.github.com/users/jdesiloniz/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "juanpedromoreno", + "id": 4879373, + "avatar_url": "https://avatars1.githubusercontent.com/u/4879373?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/juanpedromoreno", + "html_url": "https://github.com/juanpedromoreno", + "followers_url": "https://api.github.com/users/juanpedromoreno/followers", + "following_url": "https://api.github.com/users/juanpedromoreno/following{/other_user}", + "gists_url": "https://api.github.com/users/juanpedromoreno/gists{/gist_id}", + "starred_url": "https://api.github.com/users/juanpedromoreno/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/juanpedromoreno/subscriptions", + "organizations_url": "https://api.github.com/users/juanpedromoreno/orgs", + "repos_url": "https://api.github.com/users/juanpedromoreno/repos", + "events_url": "https://api.github.com/users/juanpedromoreno/events{/privacy}", + "received_events_url": "https://api.github.com/users/juanpedromoreno/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2016-11-03T19:49:56Z", + "updated_at": "2017-03-22T18:19:41Z", + "closed_at": "2017-03-22T18:19:41Z", + "body": "", + "score": 4.216856 + } + ] + } + """ + + val searchIssuesEmptyResponse = + """ + { + "total_count": 0, + "incomplete_results": false, + "items": [] + } + """ + }