From ef41767ad79a7e358b659fd62c8d830c573d4d7b Mon Sep 17 00:00:00 2001 From: Rafa Paradela Date: Thu, 12 May 2016 13:38:54 +0200 Subject: [PATCH 1/2] Add Mockito --- .../github4s/GithubResponses.scala | 39 +++------------ .../fortysevendeg/github4s/HttpClient.scala | 16 ++---- .../com/fortysevendeg/github4s/api/Auth.scala | 6 ++- .../com/fortysevendeg/github4s/app.scala | 3 +- .../github4s/free/algebra/RequestOps.scala | 49 ------------------- .../free/interpreters/Interpreters.scala | 18 ++----- src/test/scala/GHAuthSpec.scala | 2 +- src/test/scala/GHReposSpec.scala | 4 +- src/test/scala/GHUsersSpec.scala | 6 +-- 9 files changed, 25 insertions(+), 118 deletions(-) delete mode 100644 src/main/scala/com/fortysevendeg/github4s/free/algebra/RequestOps.scala diff --git a/src/main/scala/com/fortysevendeg/github4s/GithubResponses.scala b/src/main/scala/com/fortysevendeg/github4s/GithubResponses.scala index 7b1434f85..a26028ad6 100644 --- a/src/main/scala/com/fortysevendeg/github4s/GithubResponses.scala +++ b/src/main/scala/com/fortysevendeg/github4s/GithubResponses.scala @@ -16,13 +16,7 @@ object GithubResponses { type GHResponse[A] = GHException Xor GHResult[A] - - sealed trait GHResult[A] - - final case class GHItemResult[A](value: A, statusCode: Int, headers: Map[String, IndexedSeq[String]]) extends GHResult[A] - - final case class GHListResult[A](value: A, statusCode: Int, headers: Map[String, IndexedSeq[String]], decoder: Decoder[A]) extends GHResult[A] - + case class GHResult[A](value: A, statusCode: Int, headers: Map[String, IndexedSeq[String]]) sealed abstract class GHException(msg : String, cause : Option[Throwable] = None) extends Throwable(msg) { cause foreach initCause @@ -32,39 +26,18 @@ object GithubResponses { case class UnexpectedException(msg : String) extends GHException(msg) - - def toEntity[A](response: HttpResponse[String], d: Decoder[A]): GHResponse[A] = response match { - case r if r.isSuccess => { - implicit val D: Decoder[A] = d - decode[A](r.body).fold(e => JsonParsingException(e.getMessage).left[GHResult[A]], (result) => { - result match { - case Nil => Xor.Right(GHListResult(result, r.code, toLowerCase(r.headers), d)) - case _ :: _ => Xor.Right(GHListResult(result, r.code, toLowerCase(r.headers), d)) - case _ => Xor.Right(GHItemResult(result, r.code, toLowerCase(r.headers))) - } - }) - } + def toEntity[A](response: HttpResponse[String])(implicit D: Decoder[A]): GHResponse[A] = response match { + case r if r.isSuccess => decode[A](r.body) + .fold(e => JsonParsingException(e.getMessage).left[GHResult[A]], + result => Xor.Right(GHResult(result, r.code, toLowerCase(r.headers)))) case r => UnexpectedException(s"Failed invoking get with status : ${r.code}, body : \n ${r.body}").left[GHResult[A]] } def toEmpty(response: HttpResponse[String]): GHResponse[Unit] = response match { - case r if r.isSuccess => Xor.Right(GHItemResult(Unit, r.code, toLowerCase(r.headers))) + case r if r.isSuccess => Xor.Right(GHResult(Unit, r.code, toLowerCase(r.headers))) case r => UnexpectedException(s"Failed invoking get with status : ${r.code}, body : \n ${r.body}").left[GHResult[Unit]] } private def toLowerCase(headers: Map[String, IndexedSeq[String]]): Map[String, IndexedSeq[String]] = headers.map(e => (e._1.toLowerCase, e._2)) - implicit class GHEntity[A](result: GHResult[A]) { - def entity[A] = result match { - case GHListResult(r, _, _, _) => r - case GHItemResult(r, _, _) => r - } - - def statusCode[A] = result match { - case GHListResult(_, sc, _, _) => sc - case GHItemResult(_, sc, _) => sc - } - - } - } \ No newline at end of file diff --git a/src/main/scala/com/fortysevendeg/github4s/HttpClient.scala b/src/main/scala/com/fortysevendeg/github4s/HttpClient.scala index f35a40c9b..8a8a111da 100644 --- a/src/main/scala/com/fortysevendeg/github4s/HttpClient.scala +++ b/src/main/scala/com/fortysevendeg/github4s/HttpClient.scala @@ -89,20 +89,14 @@ class HttpClient { GithubResponses.toEntity(HttpRequestBuilder(buildURL(method)) .withAuth(accessToken) .withParams(params ++ pagination.fold(Map.empty[String, String])(p => Map("page" -> p.page.toString, "per_page" -> p.per_page.toString))) - .run, D) - - def getByUrl[A](accessToken: Option[String] = None, url: String, d: Decoder[A]): GHResponse[A] = - GithubResponses.toEntity(HttpRequestBuilder(url) - .withAuth(accessToken) - .run, d) - + .run) def patch[A](accessToken: Option[String] = None, method: String, data: String)(implicit D: Decoder[A]): GHResponse[A] = GithubResponses.toEntity(HttpRequestBuilder(buildURL(method)) .patchMethod .withAuth(accessToken) .withData(data) - .run, D) + .run) def put(accessToken: Option[String] = None, method: String): GHResponse[Unit] = GithubResponses.toEmpty(HttpRequestBuilder(buildURL(method)) @@ -121,7 +115,7 @@ class HttpClient { .withAuth(accessToken) .withHeaders(headers) .withData(data) - .run, D) + .run) def postAuth[A]( method: String, @@ -131,7 +125,7 @@ class HttpClient { GithubResponses.toEntity(HttpRequestBuilder(buildURL(method)) .withHeaders(headers) .withData(data) - .run, D) + .run) def postOAuth[A]( url: String, @@ -140,7 +134,7 @@ class HttpClient { GithubResponses.toEntity(HttpRequestBuilder(url) .withHeaders(Map("Accept" -> "application/json")) .withData(data) - .run, D) + .run) def delete(accessToken: Option[String] = None, method: String): GHResponse[Unit] = GithubResponses.toEmpty(HttpRequestBuilder(buildURL(method)) diff --git a/src/main/scala/com/fortysevendeg/github4s/api/Auth.scala b/src/main/scala/com/fortysevendeg/github4s/api/Auth.scala index 9885c4c17..b9ff75fdc 100644 --- a/src/main/scala/com/fortysevendeg/github4s/api/Auth.scala +++ b/src/main/scala/com/fortysevendeg/github4s/api/Auth.scala @@ -3,7 +3,7 @@ package com.fortysevendeg.github4s.api import java.util.UUID import cats.data.Xor -import com.fortysevendeg.github4s.GithubResponses.{GHItemResult, GHResponse} +import com.fortysevendeg.github4s.GithubResponses.{GHResult, GHResponse} import com.fortysevendeg.github4s.free.domain._ import com.fortysevendeg.github4s.HttpClient import io.circe.generic.auto._ @@ -21,6 +21,7 @@ object Auth { /** * Call to request a new authorization given a basic authentication, the returned object Authorization includes an * access token + * * @param username * @param password * @param scopes @@ -56,7 +57,7 @@ object Auth { scopes: List[String]): GHResponse[Authorize] = { val state = UUID.randomUUID().toString Xor.Right( - GHItemResult( + GHResult( value = Authorize(authorizeUrl.format(client_id, redirect_uri, scopes.mkString(","), state), state), statusCode = 200, headers = Map.empty)) @@ -65,6 +66,7 @@ object Auth { /** * Requests an access token based on the code retrieved in the first step of the oAuth process + * * @param client_id * @param client_secret * @param code diff --git a/src/main/scala/com/fortysevendeg/github4s/app.scala b/src/main/scala/com/fortysevendeg/github4s/app.scala index cdce007f9..28d8e2646 100644 --- a/src/main/scala/com/fortysevendeg/github4s/app.scala +++ b/src/main/scala/com/fortysevendeg/github4s/app.scala @@ -5,6 +5,5 @@ import com.fortysevendeg.github4s.free.algebra._ object app { type COGH01[A] = Coproduct[RepositoryOp, UserOp, A] - type COGH02[A] = Coproduct[RequestOp, COGH01, A] - type GitHub4s[A] = Coproduct[AuthOp, COGH02, A] + type GitHub4s[A] = Coproduct[AuthOp, COGH01, A] } \ No newline at end of file diff --git a/src/main/scala/com/fortysevendeg/github4s/free/algebra/RequestOps.scala b/src/main/scala/com/fortysevendeg/github4s/free/algebra/RequestOps.scala deleted file mode 100644 index e2b828a12..000000000 --- a/src/main/scala/com/fortysevendeg/github4s/free/algebra/RequestOps.scala +++ /dev/null @@ -1,49 +0,0 @@ -package com.fortysevendeg.github4s.free.algebra - -import cats.free.{Free, Inject} -import com.fortysevendeg.github4s.GithubResponses._ -import io.circe.Decoder - -/** Requests ops ADT - */ -sealed trait RequestOp[A] -final case class Next[A](url: String, decoder: Decoder[A], accessToken: Option[String] = None) extends RequestOp[GHResponse[A]] - - -/** Exposes Requests operations as a Free monadic algebra that may be combined with other Algebras via - * Coproduct - */ -class RequestOps[F[_]](implicit I: Inject[RequestOp, F]) { - - def next[A](url: String, decoder: Decoder[A], accessToken: Option[String] = None): Free[F, GHResponse[A]] = Free.inject[RequestOp, F](Next[A](url, decoder, accessToken)) - - def nextList[A](result: GHListResult[A]): Option[Free[F, GHResponse[A]]] = followLink[A](result, "next") - - def followLink[A](result: GHListResult[A], rel: String): Option[Free[F, GHResponse[A]]] = result match { - case GHListResult(_, _, headers, decoder) => { - for { - linkHeader <- headers.get("link") - nextLink <- extractLink(linkHeader).get(rel) - } yield next[A](nextLink, decoder) - } - case _ => None - } - - private def extractLink(rawLink : IndexedSeq[String]): Map[String, String] = - rawLink.flatMap(_.split(",").map(l => { - val Array(rawUrl, rawRel) = l.split(";") - val url = rawUrl.substring(1, rawUrl.length - 1) - val rel = rawRel.split("\"").last - (rel, url) - }).toMap).toMap - -} - - -/** Default implicit based DI factory from which instances of the RequestOps may be obtained - */ -object RequestOps { - - implicit def instance[F[_]](implicit I: Inject[RequestOp, F]): RequestOps[F] = new RequestOps[F] - -} \ No newline at end of file diff --git a/src/main/scala/com/fortysevendeg/github4s/free/interpreters/Interpreters.scala b/src/main/scala/com/fortysevendeg/github4s/free/interpreters/Interpreters.scala index 2f9a11924..36b3827a6 100644 --- a/src/main/scala/com/fortysevendeg/github4s/free/interpreters/Interpreters.scala +++ b/src/main/scala/com/fortysevendeg/github4s/free/interpreters/Interpreters.scala @@ -3,7 +3,7 @@ package com.fortysevendeg.github4s.free.interpreters import cats.{MonadError, ApplicativeError, ~>, Eval} import com.fortysevendeg.github4s.HttpClient import com.fortysevendeg.github4s.api.{Auth, Repos} -import com.fortysevendeg.github4s.app.{COGH01, COGH02, GitHub4s} +import com.fortysevendeg.github4s.app.{COGH01, GitHub4s} import com.fortysevendeg.github4s.free.algebra._ import io.circe.Decoder @@ -13,9 +13,8 @@ trait Interpreters[M[_]] { implicit A: MonadError[M, Throwable] ): GitHub4s ~> M = { - val repositoryAndUserInterpreter: COGH01 ~> M = repositoryOpsInterpreter or userOpsInterpreter - val c01nterpreter: COGH02 ~> M = requestOpsInterpreter or repositoryAndUserInterpreter - val all: GitHub4s ~> M = authOpsInterpreter or c01nterpreter + val c01interpreter: COGH01 ~> M = repositoryOpsInterpreter or userOpsInterpreter + val all: GitHub4s ~> M = authOpsInterpreter or c01interpreter all } @@ -54,17 +53,6 @@ trait Interpreters[M[_]] { } } - /** Lifts Request Ops to an effect capturing Monad such as Task via natural transformations - */ - def requestOpsInterpreter(implicit App: ApplicativeError[M, Throwable]): RequestOp ~> M = new (RequestOp ~> M) { - def apply[A](fa: RequestOp[A]): M[A] = fa match { - case Next(url: String, decoder: Decoder[A], accessToken) ⇒ { - //implicit val d: Decoder[A] = decoder - App.pureEval(Eval.later(httpClient.getByUrl(accessToken, url, decoder))) - } - } - } - } diff --git a/src/test/scala/GHAuthSpec.scala b/src/test/scala/GHAuthSpec.scala index 28332c0b9..8eac0b822 100644 --- a/src/test/scala/GHAuthSpec.scala +++ b/src/test/scala/GHAuthSpec.scala @@ -16,7 +16,7 @@ class GHAuthSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Auth >> AuthorizeUrl" should "return the expected URL for valid username" in { val response = Github().auth.authorizeUrl(validClientId, validRedirectUri, validScopes).exec[Id] response shouldBe right - response.value.entity.url.contains(validRedirectUri) shouldBe true + response.value.value.url.contains(validRedirectUri) shouldBe true response.value.statusCode shouldBe statusCodeOK } diff --git a/src/test/scala/GHReposSpec.scala b/src/test/scala/GHReposSpec.scala index 4c92143bd..f70de1130 100644 --- a/src/test/scala/GHReposSpec.scala +++ b/src/test/scala/GHReposSpec.scala @@ -11,7 +11,7 @@ class GHReposSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Repos >> Get" should "return the expected name when valid repo is provided" in { val response = Github().repos.get(validRepoOwner, validRepoName).exec[Id] response shouldBe right - response.value.entity.name shouldBe validRepoName + response.value.value.name shouldBe validRepoName response.value.statusCode shouldBe statusCodeOK } @@ -23,7 +23,7 @@ class GHReposSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Repos >> ListCommits" should "return the expected login for a valid username" in { val response = Github().repos.listCommits(validRepoOwner, validRepoName).exec[Id] response shouldBe right - response.value.entity.nonEmpty shouldBe true + response.value.value.nonEmpty shouldBe true response.value.statusCode shouldBe statusCodeOK } diff --git a/src/test/scala/GHUsersSpec.scala b/src/test/scala/GHUsersSpec.scala index 95f02e88b..579df52b5 100644 --- a/src/test/scala/GHUsersSpec.scala +++ b/src/test/scala/GHUsersSpec.scala @@ -12,7 +12,7 @@ class GHUsersSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Users >> Get" should "return the expected login for a valid username" in { val response = Github().users.get(validUsername).exec[Id] response shouldBe right - response.value.entity.login shouldBe validUsername + response.value.value.login shouldBe validUsername response.value.statusCode shouldBe statusCodeOK } @@ -29,14 +29,14 @@ class GHUsersSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Users >> GetUsers" should "return users for a valid since value" in { val response = Github().users.getUsers(validSinceInt).exec[Id] response shouldBe right - response.value.entity.nonEmpty shouldBe true + response.value.value.nonEmpty shouldBe true response.value.statusCode shouldBe statusCodeOK } it should "return error on Left when a invalid since value is provided" in { val response = Github().users.getUsers(invalidSinceInt).exec[Id] response shouldBe right - response.value.entity.nonEmpty shouldBe true + response.value.value.nonEmpty shouldBe true response.value.statusCode shouldBe statusCodeOK } From ebfec716c59fc476d8046967d067867539635608 Mon Sep 17 00:00:00 2001 From: Rafa Paradela Date: Thu, 12 May 2016 18:07:05 +0200 Subject: [PATCH 2/2] Resolves conflicts after master merging --- .../com.fortysevendeg.github4s/integration/GHAuthSpec.scala | 3 +-- .../integration/GHReposSpec.scala | 4 ++-- .../integration/GHUsersSpec.scala | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/scala/com.fortysevendeg.github4s/integration/GHAuthSpec.scala b/src/test/scala/com.fortysevendeg.github4s/integration/GHAuthSpec.scala index 4be436cbe..88610dbaf 100644 --- a/src/test/scala/com.fortysevendeg.github4s/integration/GHAuthSpec.scala +++ b/src/test/scala/com.fortysevendeg.github4s/integration/GHAuthSpec.scala @@ -3,7 +3,6 @@ package com.fortysevendeg.github4s.integration import cats.Id import cats.scalatest.{XorMatchers, XorValues} import com.fortysevendeg.github4s.Github._ -import com.fortysevendeg.github4s.GithubResponses._ import com.fortysevendeg.github4s.free.interpreters.IdInterpreters._ import com.fortysevendeg.github4s.{Github, TestUtils} import org.scalatest._ @@ -19,7 +18,7 @@ class GHAuthSpec extends FlatSpec with Matchers with XorMatchers with XorValues val response = Github().auth.authorizeUrl(validClientId, validRedirectUri, validScopes).exec[Id] response shouldBe right response.value.value.url.contains(validRedirectUri) shouldBe true - response.value.statusCode shouldBe statusCodeOK + response.value.statusCode shouldBe okStatusCode } "Auth >> GetAccessToken" should "return error on Left for invalid code value" in { diff --git a/src/test/scala/com.fortysevendeg.github4s/integration/GHReposSpec.scala b/src/test/scala/com.fortysevendeg.github4s/integration/GHReposSpec.scala index 88844b335..43768319d 100644 --- a/src/test/scala/com.fortysevendeg.github4s/integration/GHReposSpec.scala +++ b/src/test/scala/com.fortysevendeg.github4s/integration/GHReposSpec.scala @@ -15,7 +15,7 @@ class GHReposSpec extends FlatSpec with Matchers with XorMatchers with XorValues val response = Github(accessToken).repos.get(validRepoOwner, validRepoName).exec[Id] response shouldBe right - response.value.entity.name shouldBe validRepoName + response.value.value.name shouldBe validRepoName response.value.statusCode shouldBe okStatusCode } @@ -27,7 +27,7 @@ class GHReposSpec extends FlatSpec with Matchers with XorMatchers with XorValues "Repos >> ListCommits" should "return the expected list of commits for valid data" in { val response = Github(accessToken).repos.listCommits(validRepoOwner, validRepoName).exec[Id] response shouldBe right - response.value.entity.nonEmpty shouldBe true + response.value.value.nonEmpty shouldBe true response.value.statusCode shouldBe okStatusCode } diff --git a/src/test/scala/com.fortysevendeg.github4s/integration/GHUsersSpec.scala b/src/test/scala/com.fortysevendeg.github4s/integration/GHUsersSpec.scala index 9252a229d..7d3448dad 100644 --- a/src/test/scala/com.fortysevendeg.github4s/integration/GHUsersSpec.scala +++ b/src/test/scala/com.fortysevendeg.github4s/integration/GHUsersSpec.scala @@ -15,7 +15,7 @@ class GHUsersSpec extends FlatSpec with Matchers with XorMatchers with XorValues val response = Github(accessToken).users.get(validUsername).exec[Id] response shouldBe right response.value.value.login shouldBe validUsername - response.value.statusCode shouldBe statusCodeOK + response.value.statusCode shouldBe okStatusCode } it should "return error on Left for invalid username" in { @@ -32,14 +32,14 @@ class GHUsersSpec extends FlatSpec with Matchers with XorMatchers with XorValues val response = Github(accessToken).users.getUsers(validSinceInt).exec[Id] response shouldBe right response.value.value.nonEmpty shouldBe true - response.value.statusCode shouldBe statusCodeOK + response.value.statusCode shouldBe okStatusCode } it should "return error on Left when a invalid since value is provided" in { val response = Github(accessToken).users.getUsers(invalidSinceInt).exec[Id] response shouldBe right response.value.value.nonEmpty shouldBe true - response.value.statusCode shouldBe statusCodeOK + response.value.statusCode shouldBe okStatusCode } }