From 46d7e38b4651caff2a7fb9dc9ee1aa398807db44 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Tue, 9 Oct 2018 14:37:57 -0700 Subject: Add testkit module --- README.md | 1 + build.sbt | 19 +++++- .../testkit/TestRouteServiceTransport.scala | 79 ++++++++++++++++++++++ .../testkit/AsyncDatabaseBackedRouteTest.scala | 28 ++++++++ .../driver/core/testkit/DriverFunctionalTest.scala | 18 +++++ .../xyz/driver/core/testkit/FixtureDatabase.scala | 60 ++++++++++++++++ .../core/testkit/RestDatabaseResetService.scala | 70 +++++++++++++++++++ .../core/testkit/hsql/HsqlTestDatabase.scala | 26 +++++++ .../testkit/postgres/DockerPostgresDatabase.scala | 46 +++++++++++++ .../postgres/DockerPostgresFixtureDatabase.scala | 74 ++++++++++++++++++++ .../src/main/scala/xyz/driver/test/renames.scala | 46 +++++++++++++ 11 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala create mode 100644 core-testkit/src/main/scala/xyz/driver/test/renames.scala diff --git a/README.md b/README.md index c44f820..22cecd0 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Project | Description `core-reporting` | Combined tracing and logging library. `core-rest` | Abstractions to represent RESTful services, discovery and client implementations. `core-storage` | Object storage utilities. +`core-testkit` | Mixins for scalatest. `core-types` | Type definitions that are commonly used by applications. `core-util` | Other utilities that do not belong anywhere else. **Note that this is a staging place for code that does not have its own submodule yet. Do not depend on it externally!** diff --git a/build.sbt b/build.sbt index fdba59e..286e7ec 100644 --- a/build.sbt +++ b/build.sbt @@ -11,6 +11,9 @@ scalacOptions in ThisBuild in (Compile, doc) ++= Seq( s"https://github.com/drivergroup/driver-core/blob/master€{FILE_PATH}.scala" ) +val mockito = "org.mockito" % "mockito-core" % "1.9.5" +val scalatest = "org.scalatest" %% "scalatest" % "3.0.5" + // TODO these shouldn't be declared in the build scope. They should be moved to the individual // sub-projects that actually depend on them. libraryDependencies in ThisBuild ++= Seq( @@ -44,11 +47,11 @@ libraryDependencies in ThisBuild ++= Seq( "io.kamon" %% "kamon-statsd" % "1.0.0", "io.kamon" %% "kamon-system-metrics" % "1.0.0", "javax.xml.bind" % "jaxb-api" % "2.2.8", - "org.mockito" % "mockito-core" % "1.9.5" % "test", + mockito % "test", "org.scala-lang.modules" %% "scala-async" % "0.9.7", "org.scalacheck" %% "scalacheck" % "1.14.0" % "test", - "org.scalatest" %% "scalatest" % "3.0.5" % "test", "org.scalaz" %% "scalaz-core" % "7.2.24", + scalatest % "test", "xyz.driver" %% "spray-json-derivation" % "0.6.0", "xyz.driver" %% "tracing" % "0.1.2" ) @@ -84,6 +87,18 @@ lazy val `core-init` = project .enablePlugins(LibraryPlugin) .dependsOn(`core-reporting`, `core-storage`, `core-messaging`, `core-rest`, `core-database`) +lazy val `core-testkit` = project + .enablePlugins(LibraryPlugin) + .dependsOn(`core-rest`, `core-database`) + .settings( + libraryDependencies ++= Seq( + "com.spotify" % "docker-client" % "8.11.7" classifier "shaded", + "org.postgresql" % "postgresql" % "9.4.1212", + "org.scalamock" %% "scalamock-scalatest-support" % "3.6.0", + scalatest + ) + ) + lazy val core = project .in(file(".")) .enablePlugins(LibraryPlugin) diff --git a/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala b/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala new file mode 100644 index 0000000..36e3596 --- /dev/null +++ b/core-testkit/src/main/scala/akka/http/scaladsl/testkit/TestRouteServiceTransport.scala @@ -0,0 +1,79 @@ +package akka.http.scaladsl.testkit + +import akka.actor.ActorSystem +import akka.http.javadsl.testkit.DefaultHostInfo +import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.headers.{Host, RawHeader} +import akka.http.scaladsl.server._ +import akka.http.scaladsl.settings.RoutingSettings +import akka.http.scaladsl.unmarshalling.Unmarshal +import akka.stream.{ActorMaterializer, Materializer} +import org.slf4j.MDC +import xyz.driver.core.rest.errors.ExternalServiceException +import xyz.driver.core.rest.{ContextHeaders, ServiceRequestContext, ServiceTransport} + +import scala.concurrent.{ExecutionContextExecutor, Future} +import scala.concurrent.duration._ + +class TestRouteServiceTransport(route: Route, routeTimeout: FiniteDuration = 10 seconds)( + implicit executor: ExecutionContextExecutor, + system: ActorSystem) + extends ServiceTransport with RouteTestResultComponent { + + def defaultHost: DefaultHostInfo = DefaultHostInfo(Host("example.com"), securedConnection = false) + def routingLog: RoutingLog = RoutingLog(system.log) + def routingSettings: RoutingSettings = RoutingSettings.apply(system) + implicit val materializer = ActorMaterializer() + + override def sendRequestGetResponse(context: ServiceRequestContext)( + requestStub: HttpRequest): Future[HttpResponse] = { + + val request = requestStub + .withHeaders(requestStub.headers ++ context.contextHeaders.toSeq.map { + case (ContextHeaders.TrackingIdHeader, _) => + RawHeader(ContextHeaders.TrackingIdHeader, context.trackingId) + case (ContextHeaders.StacktraceHeader, _) => + RawHeader( + ContextHeaders.StacktraceHeader, + Option(MDC.get("stack")) + .orElse(context.contextHeaders.get(ContextHeaders.StacktraceHeader)) + .getOrElse("")) + case (header, headerValue) => RawHeader(header, headerValue) + }: _*) + + // Code below is forked from `akka.http.scaladsl.testkit.RouteTest.TildeArrow.injectIntoRoute`, + // because it doesn't allow to call just this code outside of the DSL for testkit tests + val routeTestResult = new RouteTestResult(routeTimeout) + + val effectiveRequest = request + + val log = routingLog.requestLog(effectiveRequest) + val ctx = new RequestContextImpl(effectiveRequest, log, routingSettings)(executor, materializer) + val sealedExceptionHandler = ExceptionHandler.default(routingSettings) + val semiSealedRoute = Directives.handleExceptions(sealedExceptionHandler)(route) + val deferrableRouteResult = semiSealedRoute(ctx) + + deferrableRouteResult.map(r => { routeTestResult.handleResult(r); routeTestResult.response }) + } + + override def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest)( + implicit mat: Materializer): Future[Unmarshal[ResponseEntity]] = { + sendRequestGetResponse(context)(requestStub) flatMap { response => + if (response.status == StatusCodes.NotFound) { + Future.successful(Unmarshal(HttpEntity.Empty: ResponseEntity)) + } else if (response.status.isFailure()) { + val serviceCalled = s"${requestStub.method} ${requestStub.uri}" + Unmarshal(response.entity).to[String] flatMap { errorString => + import spray.json._ + import xyz.driver.core.json._ + val serviceException = scala.util.Try(serviceExceptionFormat.read(errorString.parseJson)).toOption + Future.failed(ExternalServiceException(serviceCalled, errorString, serviceException)) + } + } else { + Future.successful(Unmarshal(response.entity)) + } + } + } + + override def failTest(msg: String): Nothing = throw new Exception(msg) +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala new file mode 100644 index 0000000..8ce33f6 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala @@ -0,0 +1,28 @@ +package xyz.driver.core.testkit + +import java.util.concurrent.Executors + +import akka.actor.ActorSystem +import akka.http.scaladsl.server.directives.FutureDirectives +import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} +import org.scalamock.scalatest.AsyncMockFactory +import org.scalatest.AsyncFlatSpec + +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} + +trait AsyncDatabaseBackedRouteTest + extends AsyncFlatSpec with DriverFunctionalTest with AsyncMockFactory with ScalatestRouteTest { + + def route: FutureDirectives + + val defaultTimeOut: FiniteDuration = 5.seconds + implicit def default(implicit system: ActorSystem): RouteTestTimeout = RouteTestTimeout(defaultTimeOut) + + override implicit val executor: ExecutionContextExecutor = + ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4)) + override implicit def executionContext: ExecutionContext = executor + + override def beforeAll: Unit = super.beforeAll() + override def afterAll: Unit = super.afterAll() +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala new file mode 100644 index 0000000..f4c793b --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala @@ -0,0 +1,18 @@ +package xyz.driver.core.testkit + +import com.typesafe.scalalogging.Logger +import org.scalatest.{Matchers, Suite} +import org.slf4j.helpers.NOPLogger +import xyz.driver.core.time.Time +import xyz.driver.core.time.provider.{SpecificTimeProvider, TimeProvider} + +trait DriverFunctionalTest extends Matchers with postgres.DockerPostgresFixtureDatabase { + self: Suite => + + // Needed to reset some external libraries timezones (e.g., HSQLDB) + // for local development matching CI and deployed environment + java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("Etc/UTC")) + + val log: Logger = Logger(NOPLogger.NOP_LOGGER) + val timeProvider: TimeProvider = new SpecificTimeProvider(Time(100000)) +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala new file mode 100644 index 0000000..aa07668 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala @@ -0,0 +1,60 @@ +package xyz.driver.core.testkit + +import java.nio.file.{Files, Path} + +import scala.concurrent.Await +import scala.concurrent.duration._ + +import org.scalatest.{BeforeAndAfterEach, Suite} +import slick.basic.BasicProfile +import slick.jdbc.JdbcProfile +import slick.relational.RelationalProfile + +trait FixtureDatabase[P <: BasicProfile] { self: Suite => + val profile: P + val database: P#Backend#DatabaseDef +} + +trait CreateAndDropSchemaForEach[P <: RelationalProfile] extends BeforeAndAfterEach { + self: Suite with FixtureDatabase[P] => + import profile.api._ + def schema: profile.SchemaDescription + + override protected def beforeEach() = { + Await.result(database.run(schema.create), 5.seconds) + super.beforeEach() + } + + override protected def afterEach() = { + try super.afterEach() + finally Await.result(database.run(schema.drop), 5.seconds) + } +} + +trait TruncateSchemaAfterEach[P <: RelationalProfile] extends BeforeAndAfterEach { + self: Suite with FixtureDatabase[P] => + import profile.api._ + + def schema: profile.SchemaDescription + + override protected def afterEach() = { + try super.afterEach() + finally Await.result(database.run(schema.truncate), 5.seconds) + } +} + +trait InsertBeforeEach[P <: JdbcProfile] extends BeforeAndAfterEach { self: Suite with FixtureDatabase[P] => + import profile.api._ + + val insertsFiles: Set[Path] + + def insertTestData(insertsFile: Path): DBIO[Int] = { + val rawInserts = new String(Files.readAllBytes(insertsFile), "UTF-8") + sql"#$rawInserts".asUpdate + } + + override protected def beforeEach(): Unit = { + concurrent.Await.result(database.run(DBIO.sequence(insertsFiles.toList.map(insertTestData))), 30.seconds) + super.beforeEach() + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala new file mode 100644 index 0000000..db02bbb --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala @@ -0,0 +1,70 @@ +package xyz.driver.core.testkit + +import akka.actor.ActorSystem +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import spray.json._ +import xyz.driver.core.Name +import xyz.driver.core.rest.{RestService, ServiceRequestContext, ServiceTransport} + +import scala.concurrent.{ExecutionContext, Future} +import scalaz.{ListT, OptionT} + +object RestDatabaseResetService { + import DefaultJsonProtocol._ + import xyz.driver.core.json._ + + final case class Application(name: Name[Application], tag: String) + + final case class Environment(name: Name[Environment], applications: Seq[Application], status: String) + + implicit val applicationJsonFormat = jsonFormat2(Application) + + implicit val environmentJsonFormat = jsonFormat3(Environment) +} + +class RestDatabaseResetService( + transport: ServiceTransport, + baseUri: Uri, + actorSystem: ActorSystem, + executionContext: ExecutionContext) + extends RestService { + + import DefaultJsonProtocol._ + import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ + import RestDatabaseResetService._ + + protected implicit val exec = executionContext + protected implicit val materializer = ActorMaterializer()(actorSystem) + + def getAvailableEnvironments(): ListT[Future, String] = { + val request = get(baseUri, s"/v1/environment") + listResponse[String](transport.sendRequest(new ServiceRequestContext())(request)) + } + + def getEnvironmentDetails(envName: Name[Environment]): ListT[Future, Environment] = { + val requestStub = get(baseUri, s"/v1/environment/$envName") + listResponse[Environment](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def getEnvironmentStatus(envName: Name[Environment]): OptionT[Future, String] = { + val requestStub = get(baseUri, s"/v1/environment/$envName/status") + optionalResponse[String](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def resetEnvironmentStatus(envName: Name[Environment], newStatus: String): OptionT[Future, String] = { + val requestStub = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/environment/$envName/status")) + optionalResponse[String](transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def resetEnvironmentApplicationsData(envName: Name[Environment]): OptionT[Future, Unit] = { + val requestStub = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/environment/$envName/reset")) + unitResponse(transport.sendRequest(new ServiceRequestContext())(requestStub)) + } + + def updateEnvironmentState(updatedEnvironment: Environment)( + implicit ctx: ServiceRequestContext): OptionT[Future, Environment] = { + val requestStub = postJson(baseUri, s"/v1/environment/${updatedEnvironment.name}", updatedEnvironment.toJson) + optionalResponse[Environment](transport.sendRequest(ctx)(requestStub)) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala new file mode 100644 index 0000000..8a4c3e8 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala @@ -0,0 +1,26 @@ +package xyz.driver.test.hsql + +import java.nio.file.{Files, Paths} + +import slick.dbio.DBIO + +trait HsqlTestDatabase { + def insertTestData(database: xyz.driver.core.database.Database, filePath: String): DBIO[Int] = { + import database.profile.api._ + + val file = Paths.get(filePath) + val sqlLine = new String(Files.readAllBytes(file), "UTF-8") + + val createProcedure = + sqlu"""CREATE PROCEDURE INSERT_TEST_DATA() + MODIFIES SQL DATA + BEGIN ATOMIC + #$sqlLine + END; + """ + val callProcedure = sqlu"""{call INSERT_TEST_DATA()}""" + val dropProcedure = sqlu"""drop PROCEDURE INSERT_TEST_DATA;""" + + createProcedure >> callProcedure >> dropProcedure + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala new file mode 100644 index 0000000..41e6e0d --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala @@ -0,0 +1,46 @@ +package xyz.driver.core.testkit +package postgres + +import xyz.driver.core.make + +trait DockerPostgresDatabase { + import com.spotify.docker.client._ + import com.spotify.docker.client.messages._ + + lazy val dockerClient: DockerClient = DefaultDockerClient.fromEnv().build() + + val postgresVersion: String = "9.6" + + def setupDockerDatabase( + username: String = "postgres", + password: String = "postgres", + database: String = "postgres", + hostPort: Int = 15432): String = { + import collection.JavaConverters._ + + dockerClient.pull(s"postgres:$postgresVersion") + + val portBindings: Map[String, List[PortBinding]] = Map("5432" -> List(PortBinding.of("0.0.0.0", hostPort))) + val portBindingsJava = portBindings.mapValues(_.asJava).asJava + val hostConfig = HostConfig.builder().portBindings(portBindingsJava).build() + val containerConfig = + ContainerConfig + .builder() + .hostConfig(hostConfig) + .image(s"postgres:$postgresVersion") + .exposedPorts("5432") + .env( + s"POSTGRES_USER=$username", + s"POSTGRES_DB=$database", + s"POSTGRES_PASSWORD=$password" + ) + .build() + + make(dockerClient.createContainer(containerConfig).id())(dockerClient.startContainer) + } + + def killDockerDatabase(containerId: String): Unit = { + dockerClient.killContainer(containerId) + dockerClient.removeContainer(containerId) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala new file mode 100644 index 0000000..3e00bf5 --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala @@ -0,0 +1,74 @@ +package xyz.driver.core.testkit +package postgres + +import java.net.ServerSocket + +import org.scalatest.{BeforeAndAfterAll, Suite} +import slick.jdbc.PostgresProfile +import xyz.driver.core.using + +import scala.concurrent.Await +import scala.concurrent.duration._ + +trait DockerPostgresFixtureDatabase + extends FixtureDatabase[PostgresProfile] with DockerPostgresDatabase with BeforeAndAfterAll { + self: Suite => + private val dbName: String = sys.env.getOrElse("TEST_DB_NAME", "postgres") + private val username: String = sys.env.getOrElse("TEST_DB_USERNAME", "postgres") + private val password: String = sys.env.getOrElse("TEST_DB_PASSWORD", "postgres") + private val port: Int = sys.env.get("TEST_DB_PORT").fold(getRandomPort())(_.toInt) + + private val enabled: Boolean = !sys.env.get("DISABLE_DOCKER_TEST_DB").contains("true") + + protected val connectionTimeout: Duration = 16.seconds + + final val profile = PostgresProfile + final override val database = profile.backend.Database.forURL( + driver = "org.postgresql.Driver", + url = s"jdbc:postgresql://localhost:$port/$dbName", + user = username, + password = password) + + object driverDatabase extends xyz.driver.core.database.Database { + override val profile = self.profile + override val database = self.database + } + + private def getRandomPort(): Int = using(new ServerSocket(0))(_.getLocalPort()) + + @SuppressWarnings(Array("org.wartremover.warts.Var")) + private var dockerContainerId: Option[String] = None + + private def waitForContainer(): Unit = { + import concurrent.ExecutionContext.Implicits.global + import profile.api._ + + val expiration = System.currentTimeMillis() + connectionTimeout.toMillis + + val query = sql"SELECT 1;".as[Int] + def dbReady = Await.result( + database + .run(query) + .map(_ == Vector(1)) + .recover({ case _: org.postgresql.util.PSQLException => false }), + connectionTimeout + ) + + while (!dbReady && System.currentTimeMillis() < expiration) { + Thread.sleep(100) + } + } + + override protected def beforeAll(): Unit = { + if (enabled) { + dockerContainerId = Some(super.setupDockerDatabase(username, password, dbName, port)) + waitForContainer() + } + super.beforeAll() + } + + override protected def afterAll(): Unit = { + try super.afterAll() + finally dockerContainerId.foreach(killDockerDatabase) + } +} diff --git a/core-testkit/src/main/scala/xyz/driver/test/renames.scala b/core-testkit/src/main/scala/xyz/driver/test/renames.scala new file mode 100644 index 0000000..0ebee4a --- /dev/null +++ b/core-testkit/src/main/scala/xyz/driver/test/renames.scala @@ -0,0 +1,46 @@ +package xyz.driver + +import xyz.driver.core.testkit +import slick.basic.BasicProfile +import slick.jdbc.JdbcProfile +import slick.relational.RelationalProfile + +package object test { + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type AsyncDatabaseBackedRouteTest = testkit.AsyncDatabaseBackedRouteTest + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type DriverFunctionalTest = testkit.DriverFunctionalTest + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type FixtureDatabase[P <: BasicProfile] = testkit.FixtureDatabase[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type CreateAndDropSchemaForEach[P <: RelationalProfile] = testkit.CreateAndDropSchemaForEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type TruncateSchemaAfterEach[P <: RelationalProfile] = testkit.TruncateSchemaAfterEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type InsertBeforeEach[P <: JdbcProfile] = testkit.InsertBeforeEach[P] + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + type RestDatabaseResetService = testkit.RestDatabaseResetService + + @deprecated("moved to package `xyz.driver.core.testkit`", "2.0") + val RestDatabaseResetService: testkit.RestDatabaseResetService.type = testkit.RestDatabaseResetService + +} + +package test { + package object postgres { + + @deprecated("moved to package `xyz.driver.core.testkit.postgres`", "2.0") + type DockerPostgresDatabase = xyz.driver.core.testkit.postgres.DockerPostgresDatabase + + @deprecated("moved to package `xyz.driver.core.testkit.postgres`", "2.0") + type DockerPostgresFixtureDatabase = xyz.driver.core.testkit.postgres.DockerPostgresFixtureDatabase + + } +} -- cgit v1.2.3