aboutsummaryrefslogtreecommitdiff
path: root/core-testkit/src/main/scala/xyz/driver/core/testkit
diff options
context:
space:
mode:
Diffstat (limited to 'core-testkit/src/main/scala/xyz/driver/core/testkit')
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/AsyncDatabaseBackedRouteTest.scala28
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/DriverFunctionalTest.scala18
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/FixtureDatabase.scala60
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/RestDatabaseResetService.scala70
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/hsql/HsqlTestDatabase.scala26
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresDatabase.scala46
-rw-r--r--core-testkit/src/main/scala/xyz/driver/core/testkit/postgres/DockerPostgresFixtureDatabase.scala74
7 files changed, 322 insertions, 0 deletions
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)
+ }
+}