From b8c8ee62838ae9136b4644a5a6f455ddb862712d Mon Sep 17 00:00:00 2001 From: Gabriele Petronella Date: Thu, 19 Mar 2020 18:00:32 +0100 Subject: [PATCH] Add unit tests to tapiro --- project/Dependencies.scala | 6 +- tapiro/ci/test.sh | 2 +- .../main/scala/io/buildo/tapiro/Util.scala | 9 +- .../core/src/test/resources/log4j2-test.xml | 6 + .../scala/io/buildo/tapiro/FileLayout.scala | 58 ++++++++ .../scala/io/buildo/tapiro/TapiroSuite.scala | 135 ++++++++++++++++++ .../scala/io/buildo/tapiro/SbtTapiro.scala | 2 +- .../src/sbt-test/sbt-tapiro/simple/build.sbt | 2 +- .../simple/src/main/resources/logback.xml | 3 - 9 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 tapiro/core/src/test/resources/log4j2-test.xml create mode 100644 tapiro/core/src/test/scala/io/buildo/tapiro/FileLayout.scala create mode 100644 tapiro/core/src/test/scala/io/buildo/tapiro/TapiroSuite.scala delete mode 100644 tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/src/main/resources/logback.xml diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5c3cf37c..017e3f1b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -83,6 +83,7 @@ object Dependencies { val tapirHttp4s = "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % V.tapir val munit = "org.scalameta" %% "munit" % V.munit val munitScalaCheck = "org.scalameta" %% "munit-scalacheck" % V.munit + val log4j = "org.apache.logging.log4j" % "log4j-api" % "2.13.1" val enumeroDependencies = List( munit, @@ -186,11 +187,14 @@ object Dependencies { val tapiroCoreDependencies = List( sbtLogging, + log4j, scalameta, scalafmtCore, circeCore, pprint, - ) + ) ++ List( + munit, + ).map(_ % Test) val docsDependencies = List( plantuml, diff --git a/tapiro/ci/test.sh b/tapiro/ci/test.sh index b7a11e96..d6266686 100755 --- a/tapiro/ci/test.sh +++ b/tapiro/ci/test.sh @@ -4,4 +4,4 @@ set -e apk add --no-cache curl -sbt -batch ';tapiroCore/compile ;sbt-tapiro/scripted' # TODO(claudio): compile to test once we have them +sbt -batch ';tapiroCore/test ;sbt-tapiro/scripted' diff --git a/tapiro/core/src/main/scala/io/buildo/tapiro/Util.scala b/tapiro/core/src/main/scala/io/buildo/tapiro/Util.scala index 563b7620..aceb3fd5 100644 --- a/tapiro/core/src/main/scala/io/buildo/tapiro/Util.scala +++ b/tapiro/core/src/main/scala/io/buildo/tapiro/Util.scala @@ -12,12 +12,9 @@ import scala.meta._ import scala.util.control.NonFatal import java.nio.file.Paths import java.nio.file.Files - import cats.data.NonEmptyList - import MetarpheusHelper._ - -import sbt.internal.util.ManagedLogger +import org.apache.logging.log4j.LogManager import Meta.typeNameString @@ -36,9 +33,11 @@ object TapiroRouteError { case class TapiroRoute(route: Route, error: TapiroRouteError) -class Util(logger: ManagedLogger) { +class Util() { import Formatter.format + val logger = LogManager.getLogger("io.buildo.tapiro") + def createFiles( routesPaths: List[String], modelsPaths: List[String], diff --git a/tapiro/core/src/test/resources/log4j2-test.xml b/tapiro/core/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..c36ec960 --- /dev/null +++ b/tapiro/core/src/test/resources/log4j2-test.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tapiro/core/src/test/scala/io/buildo/tapiro/FileLayout.scala b/tapiro/core/src/test/scala/io/buildo/tapiro/FileLayout.scala new file mode 100644 index 00000000..d5382e43 --- /dev/null +++ b/tapiro/core/src/test/scala/io/buildo/tapiro/FileLayout.scala @@ -0,0 +1,58 @@ +package io.buildo.tapiro + +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.StandardOpenOption +import scala.meta.io.AbsolutePath + +//TODO(gabro): add Metals notice +object FileLayout { + + def mapFromString(layout: String): Map[String, String] = { + if (!layout.trim.isEmpty) { + val lines = layout.replaceAllLiterally("\r\n", "\n") + lines + .split("(?=\n/[^/])") + .map { row => + row.stripPrefix("\n").split("\n", 2).toList match { + case path :: contents :: Nil => + path.stripPrefix("/") -> contents + case els => + throw new IllegalArgumentException( + s"Unable to split argument info path/contents! \n$els", + ) + } + } + .toMap + } else { + Map.empty + } + } + + def fromString( + layout: String, + root: AbsolutePath = AbsolutePath(Files.createTempDirectory("tapiro")), + charset: Charset = StandardCharsets.UTF_8, + ): AbsolutePath = { + if (!layout.trim.isEmpty) { + mapFromString(layout).foreach { + case (path, contents) => + val file = + path.split("/").foldLeft(root)(_.resolve(_)) + val parent = file.toNIO.getParent + if (!Files.exists(parent)) { // cannot create directories when parent is a symlink + Files.createDirectories(parent) + } + Files.deleteIfExists(file.toNIO) + Files.write( + file.toNIO, + contents.getBytes(charset), + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + ) + } + } + root + } +} diff --git a/tapiro/core/src/test/scala/io/buildo/tapiro/TapiroSuite.scala b/tapiro/core/src/test/scala/io/buildo/tapiro/TapiroSuite.scala new file mode 100644 index 00000000..d4b976f7 --- /dev/null +++ b/tapiro/core/src/test/scala/io/buildo/tapiro/TapiroSuite.scala @@ -0,0 +1,135 @@ +package io.buildo.tapiro + +import java.nio.file.Files + +class TapiroSuite extends munit.FunSuite { + + check( + "http4s", + Server.Http4s, + "src/main/scala/schools/endpoints", + """ + |/src/main/scala/schools/SchoolController.scala + |package schools + | + |case class School(id: Long, name: String) + | + |sealed trait SchoolReadError + |object SchoolReadError { + | case object NotFound extends SchoolReadError + |} + | + |trait SchoolController[F[_], T] { + | @query + | def read(id: Long): F[Either[SchoolReadError, School]] + |} + |""".stripMargin, + """ + |/src/main/scala/schools/endpoints/SchoolControllerTapirEndpoints.scala + |//---------------------------------------------------------- + |// This code was generated by tapiro. + |// Changes to this file may cause incorrect behavior + |// and will be lost if the code is regenerated. + |//---------------------------------------------------------- + | + |package endpoints + |import schools._ + |import sttp.tapir._ + |import sttp.tapir.Codec.{JsonCodec, PlainCodec} + |import sttp.model.StatusCode + | + |trait SchoolControllerTapirEndpoints[AuthToken] { + | val read: Endpoint[Long, SchoolReadError, School, Nothing] + |} + | + |object SchoolControllerTapirEndpoints { + | + | def create[AuthToken](statusCodes: String => StatusCode)( + | implicit codec0: JsonCodec[School], + | codec1: JsonCodec[SchoolReadError.NotFound.type], + | codec2: PlainCodec[Long] + | ) = new SchoolControllerTapirEndpoints[AuthToken] { + | override val read: Endpoint[Long, SchoolReadError, School, Nothing] = + | endpoint.get + | .in("read") + | .in(query[Long]("id")) + | .errorOut( + | oneOf[SchoolReadError]( + | statusMapping( + | statusCodes("NotFound"), + | jsonBody[SchoolReadError.NotFound.type] + | ) + | ) + | ) + | .out(jsonBody[School]) + | } + |} + | + |/src/main/scala/schools/endpoints/SchoolControllerHttpEndpoints.scala + |//---------------------------------------------------------- + |// This code was generated by tapiro. + |// Changes to this file may cause incorrect behavior + |// and will be lost if the code is regenerated. + |//---------------------------------------------------------- + | + |package endpoints + |import schools._ + |import cats.effect._ + |import cats.implicits._ + |import cats.data.NonEmptyList + |import org.http4s._ + |import org.http4s.server.Router + |import sttp.tapir.server.http4s._ + |import sttp.tapir.Codec.{JsonCodec, PlainCodec} + |import sttp.model.StatusCode + | + |object SchoolControllerHttpEndpoints { + | + | def routes[F[_]: Sync, AuthToken]( + | controller: SchoolController[F, AuthToken], + | statusCodes: String => StatusCode = _ => StatusCode.UnprocessableEntity + | )( + | implicit codec0: JsonCodec[School], + | codec1: JsonCodec[SchoolReadError.NotFound.type], + | codec2: PlainCodec[Long], + | cs: ContextShift[F] + | ): HttpRoutes[F] = { + | val endpoints = + | SchoolControllerTapirEndpoints.create[AuthToken](statusCodes) + | val read = endpoints.read.toRoutes(controller.read) + | Router("/SchoolController" -> NonEmptyList(read, List()).reduceK) + | } + |} + |""".stripMargin, + ) + + def check( + name: String, + server: Server, + endpointDirectory: String, + layout: String, + expectedLayout: String, + `package`: List[String] = List("endpoints"), + )( + implicit loc: munit.Location, + ): Unit = + test(name) { + val projectRoot = FileLayout.fromString(layout) + val tapiro = new Util() + tapiro.createFiles( + List(projectRoot.toString), + List(projectRoot.toString), + projectRoot.resolve(endpointDirectory).toString, + `package`, + server, + ) + + FileLayout.mapFromString(expectedLayout).map { + case (path, content) => + val filePath = path.split("/").foldLeft(projectRoot)(_.resolve(_)) + assertNoDiff(Files.readString(filePath.toNIO), content) + } + + } + +} diff --git a/tapiro/sbt-tapiro/src/main/scala/io/buildo/tapiro/SbtTapiro.scala b/tapiro/sbt-tapiro/src/main/scala/io/buildo/tapiro/SbtTapiro.scala index c020b180..a65c0cf8 100644 --- a/tapiro/sbt-tapiro/src/main/scala/io/buildo/tapiro/SbtTapiro.scala +++ b/tapiro/sbt-tapiro/src/main/scala/io/buildo/tapiro/SbtTapiro.scala @@ -31,7 +31,7 @@ object SbtTapiro extends AutoPlugin { override val projectSettings = inConfig(Compile)( Seq( tapiro := { - (new Util(streams.value.log)).createFiles( + new Util().createFiles( tapiroRoutesPaths.in(tapiro).value.map(s => (scalaSource.value / s).toString), tapiroModelsPaths.in(tapiro).value.map(s => (scalaSource.value / s).toString), (scalaSource.value / tapiroOutputPath.in(tapiro).value).toString, diff --git a/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/build.sbt b/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/build.sbt index 6b2a0c85..3fde7772 100644 --- a/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/build.sbt +++ b/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/build.sbt @@ -16,7 +16,7 @@ lazy val root = (project in file(".")) "com.softwaremill.sttp.tapir" %% "tapir-core" % "0.12.15", "org.http4s" %% "http4s-blaze-server" % http4sVersion, "org.http4s" %% "http4s-circe" % http4sVersion, - "ch.qos.logback" % "logback-classic" % "1.2.3", + "org.apache.logging.log4j" % "log4j-core" % "2.13.1", ) ++ Seq( "io.circe" %% "circe-core", "io.circe" %% "circe-generic", diff --git a/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/src/main/resources/logback.xml b/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/src/main/resources/logback.xml deleted file mode 100644 index d0cec6d3..00000000 --- a/tapiro/sbt-tapiro/src/sbt-test/sbt-tapiro/simple/src/main/resources/logback.xml +++ /dev/null @@ -1,3 +0,0 @@ - - -