diff --git a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala index f8a45c93..bef1a678 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/auth.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/auth.scala @@ -72,6 +72,7 @@ object auth { Sync[F].pure(Response(Status.Unauthorized)) } } + case None => Sync[F].pure(Response(Status.Unauthorized)) } } case _ => Sync[F].pure(Response(Status.Unauthorized)) diff --git a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala index 32a5d7b6..0e440b9c 100644 --- a/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala +++ b/server/src/main/scala/org/scalaexercises/evaluator/evaluation.scala @@ -44,10 +44,13 @@ class Evaluator[F[_]: Sync](timeout: FiniteDuration = 20.seconds)( def dependencyToModule(dependency: Dependency): coursier.Dependency = { val exclusions: Set[(Organization, ModuleName)] = - dependency.exclusions.toSet - .flatMap(_.map { - case Exclusion(org, mod) => (Organization(org), ModuleName(mod)) - }) + dependency.exclusions + .fold(List[(Organization, ModuleName)]())( + _.map( + ex => (Organization(ex.organization), ModuleName(ex.moduleName)) + )) + .toSet + coursier.Dependency .of( Module(Organization(dependency.groupId), ModuleName(dependency.artifactId)), diff --git a/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala b/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala index 0414bbd2..ae78d057 100644 --- a/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala +++ b/server/src/test/scala/org/scalaexercises/evaluator/EvalEndpointSpec.scala @@ -9,19 +9,19 @@ package org.scalaexercises.evaluator import java.nio.charset.StandardCharsets +import cats.effect.IO import io.circe.generic.auto._ import io.circe.syntax._ import org.http4s.dsl._ import org.http4s.headers._ import org.http4s.{Status => HttpStatus, _} +import org.http4s.dsl.io._ import org.scalaexercises.evaluator.helper._ import org.scalatest._ import pdi.jwt.{Jwt, JwtAlgorithm} import scodec.bits.ByteVector -import scalaz.stream.Process.emit - -class EvalEndpointSpec extends FunSpec with Matchers { +class EvalEndpointSpec extends FunSpec with Matchers with Implicits { import EvalResponse.messages._ import auth._ @@ -33,29 +33,27 @@ class EvalEndpointSpec extends FunSpec with Matchers { val invalidToken: String = java.util.UUID.randomUUID.toString - def serve(evalRequest: EvalRequest, authHeader: Header): Response = - evalService + val evaluator = new Evaluator[IO] + + val server = auth[IO](service[IO].httpApp(evaluator)) + + def serve(evalRequest: EvalRequest, authHeader: Header): Response[IO] = + server .run( - Request( - POST, - Uri(path = "/eval"), - body = emit( - ByteVector.view( - evalRequest.asJson.noSpaces.getBytes(StandardCharsets.UTF_8) - ) - ) - ).putHeaders(authHeader)) - .unsafePerformSync + Request[IO](POST, Uri(path = "/eval")) + .withEntity(evalRequest) + .putHeaders(authHeader)) + .unsafeRunSync() def verifyEvalResponse( - response: Response, + response: Response[IO], expectedStatus: HttpStatus, expectedValue: Option[String] = None, expectedMessage: String ): Assertion = { response.status should be(expectedStatus) - val evalResponse = response.as[EvalResponse].unsafePerformSync + val evalResponse = response.as[EvalResponse].unsafeRunSync() evalResponse.value should be(expectedValue) evalResponse.msg should be(expectedMessage) } @@ -136,10 +134,12 @@ class EvalEndpointSpec extends FunSpec with Matchers { EvalRequest( code = exerciseContentCode(true), resolvers = commonResolvers, - dependencies = List(Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - exercisesVersion)) ++ scalaDependencies(Scala211) + dependencies = List( + Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion, + Some(List(Exclusion("io.monix", "monix_2.11"))))) ++ scalaDependencies(Scala211) ), `X-Scala-Eval-Api-Token`(validToken) ), @@ -155,10 +155,12 @@ class EvalEndpointSpec extends FunSpec with Matchers { EvalRequest( code = exerciseContentCode(false), resolvers = commonResolvers, - dependencies = List(Dependency( - "org.scala-exercises", - "exercises-stdlib_2.11", - exercisesVersion)) ++ scalaDependencies(Scala211) + dependencies = List( + Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion, + Some(List(Exclusion("io.monix", "monix_2.11"))))) ++ scalaDependencies(Scala211) ), `X-Scala-Eval-Api-Token`(validToken) ), diff --git a/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala b/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala index b6cc679e..064458f4 100644 --- a/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala +++ b/server/src/test/scala/org/scalaexercises/evaluator/EvaluatorSpec.scala @@ -7,22 +7,21 @@ package org.scalaexercises.evaluator -import monix.execution.Scheduler import org.scalaexercises.evaluator.helper._ import org.scalatest._ import scala.concurrent.duration._ import scala.language.postfixOps -class EvaluatorSpec extends FunSpec with Matchers { - implicit val scheduler: Scheduler = Scheduler.io("exercises-spec") - val evaluator = new Evaluator(20 seconds) +class EvaluatorSpec extends FunSpec with Matchers with Implicits { + + val evaluator = new Evaluator(20 seconds) describe("evaluation") { it("can evaluate simple expressions, for Scala 2.11") { val result: EvalResult[Int] = evaluator .eval("{ 41 + 1 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala211)) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, 42, _) ⇒ @@ -32,7 +31,7 @@ class EvaluatorSpec extends FunSpec with Matchers { it("can evaluate simple expressions, for Scala 2.12") { val result: EvalResult[Int] = evaluator .eval("{ 41 + 1 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala212)) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, 42, _) ⇒ @@ -45,7 +44,7 @@ class EvaluatorSpec extends FunSpec with Matchers { "{ while(true) {}; 123 }", remotes = commonResolvers, dependencies = scalaDependencies(Scala211)) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case Timeout(_) ⇒ @@ -70,7 +69,7 @@ Xor.Right(42).toOption.get remotes = remotes, dependencies = dependencies ) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, 42, _) => @@ -86,7 +85,7 @@ Xor.Right(42).toOption.get remotes = commonResolvers, dependencies = fetchLibraryDependencies(toScalaVersion(BuildInfo.scalaVersion)) ) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, _, _) => @@ -113,14 +112,14 @@ Eval.now(42).value remotes = remotes, dependencies = dependencies1 ) - .unsafePerformSync + .unsafeRunSync() val result2: EvalResult[Int] = evaluator .eval( code, remotes = remotes, dependencies = dependencies2 ) - .unsafePerformSync + .unsafeRunSync() result1 should matchPattern { case EvalSuccess(_, 42, _) => @@ -133,7 +132,11 @@ Eval.now(42).value it("can run code from the exercises content") { val code = exerciseContentCode(true) val dependencies = List( - Dependency("org.scala-exercises", "exercises-stdlib_2.11", exercisesVersion) + Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion, + Some(List(Exclusion("io.monix", "monix_2.11")))) ) ++ scalaDependencies(Scala211) val result: EvalResult[Unit] = evaluator @@ -142,7 +145,7 @@ Eval.now(42).value remotes = commonResolvers, dependencies = dependencies ) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, (), _) => @@ -152,7 +155,11 @@ Eval.now(42).value it("captures exceptions when running the exercises content") { val dependencies = List( - Dependency("org.scala-exercises", "exercises-stdlib_2.11", exercisesVersion) + Dependency( + "org.scala-exercises", + "exercises-stdlib_2.11", + exercisesVersion, + Some(List(Exclusion("io.monix", "monix_2.11")))) ) ++ scalaDependencies(Scala211) val result: EvalResult[Unit] = evaluator @@ -161,7 +168,7 @@ Eval.now(42).value remotes = commonResolvers, dependencies = dependencies ) - .unsafePerformSync + .unsafeRunSync() result shouldBe a[EvalRuntimeError[_]] } @@ -173,7 +180,7 @@ Eval.now(42).value val result: EvalResult[Unit] = evaluator .eval(code, remotes = remotes, dependencies = dependencies) - .unsafePerformSync + .unsafeRunSync() result should matchPattern { case EvalSuccess(_, 42, _) => diff --git a/server/src/test/scala/org/scalaexercises/evaluator/Implicits.scala b/server/src/test/scala/org/scalaexercises/evaluator/Implicits.scala new file mode 100644 index 00000000..ac0daef0 --- /dev/null +++ b/server/src/test/scala/org/scalaexercises/evaluator/Implicits.scala @@ -0,0 +1,26 @@ +/* + * + * scala-exercises - evaluator-server + * Copyright (C) 2015-2019 47 Degrees, LLC. + * + */ + +package org.scalaexercises.evaluator + +import cats.effect.{IO, Timer} +import coursier.util.Sync +import coursier.interop.cats._ + +import scala.concurrent.ExecutionContext + +trait Implicits { + + val EC = ExecutionContext.global + + implicit val timer = IO.timer(EC) + + implicit val CS = IO.contextShift(EC) + + implicit val sync = Sync[IO] + +} diff --git a/server/src/test/scala/org/scalaexercises/evaluator/helper.scala b/server/src/test/scala/org/scalaexercises/evaluator/helper.scala index 97e59c64..39fcaafa 100644 --- a/server/src/test/scala/org/scalaexercises/evaluator/helper.scala +++ b/server/src/test/scala/org/scalaexercises/evaluator/helper.scala @@ -10,10 +10,10 @@ package org.scalaexercises.evaluator object helper { val remotes: List[String] = "https://oss.sonatype.org/content/repositories/releases/" :: Nil - val exercisesVersion: String = "0.4.1-SNAPSHOT" + val exercisesVersion: String = "0.5.0-SNAPSHOT" sealed abstract class ScalaVersion(val version: String) - case object Scala211 extends ScalaVersion("2.11.8") + case object Scala211 extends ScalaVersion("2.11.11") case object Scala212 extends ScalaVersion("2.12.1") def toScalaVersion(v: String): ScalaVersion = v match { @@ -30,17 +30,16 @@ object helper { def scalaDependencies(scala: ScalaVersion): List[Dependency] = List( Dependency("org.scala-lang", s"scala-library", s"${scala.version}"), - Dependency("org.scala-lang", s"scala-api", s"${scala.version}"), Dependency("org.scala-lang", s"scala-reflect", s"${scala.version}"), Dependency("org.scala-lang", s"scala-compiler", s"${scala.version}"), - Dependency("org.scala-lang", "scala-xml", s"${scala.version}") + Dependency("org.scala-lang.modules", s"scala-xml_${scala.version.substring(0, 4)}", "1.2.0") ) def fetchLibraryDependencies(scala: ScalaVersion): List[Dependency] = { val sv = scala.version List( - Dependency("com.fortysevendeg", s"fetch_${sv.substring(0, 4)}", "0.4.0"), - Dependency("com.fortysevendeg", s"fetch-monix_${sv.substring(0, 4)}", "0.4.0") + Dependency("com.47deg", s"fetch_${sv.substring(0, 4)}", "1.2.0"), + Dependency("com.47deg", s"fetch-monix_${sv.substring(0, 4)}", "0.7.3") ) ++ scalaDependencies(scala) } diff --git a/smoketests/src/test/scala/org/scalaexercises/evaluator/Smoketests.scala b/smoketests/src/test/scala/org/scalaexercises/evaluator/Smoketests.scala index 1c6de780..ac762c53 100644 --- a/smoketests/src/test/scala/org/scalaexercises/evaluator/Smoketests.scala +++ b/smoketests/src/test/scala/org/scalaexercises/evaluator/Smoketests.scala @@ -5,28 +5,33 @@ package org.scalaexercises.evaluator +import cats.Traverse.ops.toAllTraverseOps +import cats.effect.{IO, Sync} import io.circe.Printer import org.scalatest._ import org.http4s._ import org.http4s.client.blaze._ import org.http4s.circe._ import io.circe.generic.auto._ +import org.http4s.client.Client import org.scalaexercises.evaluator.helper._ import scala.concurrent.duration._ import pdi.jwt.{Jwt, JwtAlgorithm} -class Smoketests extends FunSpec with Matchers with CirceInstances { +import scala.concurrent.ExecutionContext - val evaluatorUrl: Uri = (toScalaVersion(BuildInfo.scalaVersion) match { - case Scala211 => Uri.fromString("https://scala-evaluator.herokuapp.com/eval") - case _ => Uri.fromString("https://scala-evaluator-212.herokuapp.com/eval") - }).toOption - .getOrElse( - throw new RuntimeException( - s"Unable to parse the scala evaluator url for scala version ${BuildInfo.scalaVersion}" - ) - ) +class Smoketests extends FunSpec with Matchers with CirceInstances with Implicits { + + val evaluatorUrl: Uri = IO + .fromEither(toScalaVersion(BuildInfo.scalaVersion) match { + case Scala211 => Uri.fromString("https://scala-evaluator.herokuapp.com/eval") + case _ => Uri.fromString("https://scala-evaluator-212.herokuapp.com/eval") + }) + .handleErrorWith(_ => + IO.raiseError(new RuntimeException( + s"Unable to parse the scala evaluator url for scala version ${BuildInfo.scalaVersion}"))) + .unsafeRunSync() case class EvaluatorResponse( msg: String, @@ -34,8 +39,8 @@ class Smoketests extends FunSpec with Matchers with CirceInstances { valueType: String, compilationInfos: Map[String, String]) - implicit val decoder: EntityDecoder[EvaluatorResponse] = - jsonOf[EvaluatorResponse] + implicit def decoder[F[_]: Sync]: EntityDecoder[F, EvaluatorResponse] = + jsonOf[F, EvaluatorResponse] val validToken = Jwt.encode("""{"user": "scala-exercises"}""", auth.secretKey, JwtAlgorithm.HS256) @@ -44,16 +49,18 @@ class Smoketests extends FunSpec with Matchers with CirceInstances { expectation: EvaluatorResponse => Unit, failExpectation: Throwable => Unit = fail(_)): Unit = { - val request = new Request( - method = Method.POST, - uri = evaluatorUrl, - headers = Headers(headers) - ).withBody(s"""{"resolvers" : [], "dependencies" : [], "code" : "$code"}""") + val request = Request[IO](method = Method.POST, uri = evaluatorUrl) + .withEntity(s"""{"resolvers" : [], "dependencies" : [], "code" : "$code"}""") + .withHeaders(Headers.of(headers: _*)) - val task = client.expect[EvaluatorResponse](request) + def task(client: Client[IO]) = client.expect[EvaluatorResponse](request) - val response = task.unsafePerformSyncAttemptFor(60.seconds) - response.fold(failExpectation, expectation) + client + .use(task) + .attempt + .map(_.fold(failExpectation, expectation)) + .timeout(60.seconds) + .unsafeRunSync() } val headers = List( @@ -61,7 +68,7 @@ class Smoketests extends FunSpec with Matchers with CirceInstances { Header("x-scala-eval-api-token", validToken).parsed ) - val client = PooledHttp1Client() + val client = BlazeClientBuilder[IO](ExecutionContext.global).resource describe("Querying the /eval endpoint") { it("should succeed for a simple request") { @@ -90,5 +97,5 @@ class Smoketests extends FunSpec with Matchers with CirceInstances { } } - override protected def defaultPrinter: Printer = Printer.noSpaces.copy(dropNullKeys = true) + override protected def defaultPrinter: Printer = Printer.noSpaces.copy(dropNullValues = true) }