From 610fdb07946532c0267f961cb46eb8d7925fbbff Mon Sep 17 00:00:00 2001 From: Dimitrije Bulaja Date: Thu, 15 Dec 2022 12:05:30 +0100 Subject: [PATCH] (test): Support integration tests for HttpElasticExecutor methods (#24) --- .../zio/elasticsearch/HttpExecutorSpec.scala | 139 +++++++++++++++++- .../zio/elasticsearch/IntegrationSpec.scala | 4 + .../zio/elasticsearch/ElasticConfig.scala | 6 +- .../elasticsearch/HttpElasticExecutor.scala | 19 +-- 4 files changed, 150 insertions(+), 18 deletions(-) diff --git a/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala b/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala index c26ea94d9..6b136fbc9 100644 --- a/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala +++ b/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala @@ -1,7 +1,12 @@ package zio.elasticsearch +import sttp.client3.httpclient.zio.HttpClientZioBackend +import sttp.client3.{SttpBackend, basicRequest} +import sttp.model.StatusCode.{NotFound, Ok} +import zio.elasticsearch.ElasticConfig.Default +import zio.{Task, ZIO} import zio.elasticsearch.ElasticError.DocumentRetrievingError.{DecoderError, DocumentNotFound} -import zio.test.Assertion.equalTo +import zio.test.Assertion.{equalTo, isFalse, isLeft, isRight, isTrue, isUnit} import zio.test.TestAspect.nondeterministic import zio.test._ @@ -17,13 +22,13 @@ object HttpExecutorSpec extends IntegrationSpec { document <- ElasticRequest.getById[CustomerDocument](index, documentId).execute } yield document - assertZIO(result)(Assertion.isRight(equalTo(customer))) + assertZIO(result)(isRight(equalTo(customer))) } }, test("return DocumentNotFound if the document does not exist") { checkOnce(genDocumentId) { documentId => assertZIO(ElasticRequest.getById[CustomerDocument](index, documentId).execute)( - Assertion.isLeft(equalTo(DocumentNotFound)) + isLeft(equalTo(DocumentNotFound)) ) } }, @@ -34,9 +39,131 @@ object HttpExecutorSpec extends IntegrationSpec { document <- ElasticRequest.getById[CustomerDocument](index, documentId).execute } yield document - assertZIO(result)(Assertion.isLeft(equalTo(DecoderError(".address(missing)")))) + assertZIO(result)(isLeft(equalTo(DecoderError(".address(missing)")))) } } - ) @@ nondeterministic - ).provideShared(elasticsearchLayer) + ), + suite("creating document")( + test("successfully create document") { + checkOnce(genCustomer) { customer => + val result = for { + docId <- ElasticRequest.create[CustomerDocument](index, customer).execute + res <- ElasticRequest.getById[CustomerDocument](index, docId.getOrElse(DocumentId(""))).execute + } yield res + + assertZIO(result)(isRight(equalTo(customer))) + } + }, + test("successfully create document with ID given") { + checkOnce(genDocumentId, genCustomer) { (documentId, customer) => + val result = for { + _ <- ElasticRequest.create[CustomerDocument](index, documentId, customer).execute + doc <- ElasticRequest.getById[CustomerDocument](index, documentId).execute + } yield doc + + assertZIO(result)(isRight(equalTo(customer))) + } + }, + test("fail to create document with ID given") { + checkOnce(genDocumentId, genCustomer, genCustomer) { (documentId, customer1, customer2) => + val result = for { + _ <- ElasticRequest.upsert[CustomerDocument](index, documentId, customer1).execute + _ <- ElasticRequest.create[CustomerDocument](index, documentId, customer2).execute + doc <- ElasticRequest.getById[CustomerDocument](index, documentId).execute + } yield doc + + assertZIO(result)(isRight(equalTo(customer1))) + } + } + ), + suite("creating or updating document")( + test("successfully create document") { + checkOnce(genDocumentId, genCustomer) { (documentId, customer) => + val result = for { + _ <- ElasticRequest.upsert[CustomerDocument](index, documentId, customer).execute + doc <- ElasticRequest.getById[CustomerDocument](index, documentId).execute + } yield doc + + assertZIO(result)(isRight(equalTo(customer))) + } + }, + test("successfully update document") { + checkOnce(genDocumentId, genCustomer, genCustomer) { (documentId, customer1, customer2) => + val result = for { + _ <- ElasticRequest.create[CustomerDocument](index, documentId, customer1).execute + _ <- ElasticRequest.upsert[CustomerDocument](index, documentId, customer2).execute + doc <- ElasticRequest.getById[CustomerDocument](index, documentId).execute + } yield doc + + assertZIO(result)(isRight(equalTo(customer2))) + } + } + ), + suite("finding document")( + test("return true if the document exists") { + checkOnce(genDocumentId, genCustomer) { (documentId, customer) => + val result = for { + _ <- ElasticRequest.upsert[CustomerDocument](index, documentId, customer).execute + exists <- ElasticRequest.exists(index, documentId).execute + } yield exists + + assertZIO(result)(isTrue) + } + }, + test("return false if the document does not exist") { + checkOnce(genDocumentId) { documentId => + assertZIO(ElasticRequest.exists(index, documentId).execute)(isFalse) + } + } + ), + suite("deleting document by ID")( + test("return unit if document deletion was successful") { + checkOnce(genDocumentId, genCustomer) { (documentId, customer) => + val result = for { + _ <- ElasticRequest.upsert[CustomerDocument](index, documentId, customer).execute + res <- ElasticRequest.deleteById(index, documentId).execute + } yield res + + assertZIO(result)(isRight(isUnit)) + } + }, + test("return DocumentNotFound if the document does not exist") { + checkOnce(genDocumentId) { documentId => + assertZIO(ElasticRequest.deleteById(index, documentId).execute)(isLeft(equalTo(DocumentNotFound))) + } + } + ), + suite("creating index")( + test("return true if creation was successful") { + checkOnce(genIndexName) { name => + val result = for { + _ <- ElasticRequest.createIndex(name, None).execute + sttp <- ZIO.service[SttpBackend[Task, Any]] + indexExists <- basicRequest + .head(Default.uri.withPath(name.toString)) + .send(sttp) + .map(_.code.equals(Ok)) + } yield indexExists + + assertZIO(result)(isTrue) + } + } + ), + suite("delete index")( + test("return true if deletion was successful") { + checkOnce(genIndexName) { name => + val result = for { + _ <- ElasticRequest.deleteIndex(name).execute + sttp <- ZIO.service[SttpBackend[Task, Any]] + deleted <- basicRequest + .head(Default.uri.withPath(name.toString)) + .send(sttp) + .map(_.code.equals(NotFound)) + } yield deleted + + assertZIO(result)(isTrue) + } + } + ) + ).provideShared(elasticsearchLayer, HttpClientZioBackend.layer()) @@ nondeterministic } diff --git a/modules/library/src/it/scala/zio/elasticsearch/IntegrationSpec.scala b/modules/library/src/it/scala/zio/elasticsearch/IntegrationSpec.scala index 1a19661a9..2551261f6 100644 --- a/modules/library/src/it/scala/zio/elasticsearch/IntegrationSpec.scala +++ b/modules/library/src/it/scala/zio/elasticsearch/IntegrationSpec.scala @@ -2,6 +2,7 @@ package zio.elasticsearch import sttp.client3.httpclient.zio.HttpClientZioBackend import zio.ZLayer +import zio.prelude.Newtype.unsafeWrap import zio.test.CheckVariants.CheckN import zio.test.{Gen, ZIOSpecDefault, checkN} @@ -11,6 +12,9 @@ trait IntegrationSpec extends ZIOSpecDefault { val index: IndexName = IndexName("users") + def genIndexName: Gen[Any, IndexName] = + Gen.stringBounded(10, 40)(Gen.alphaChar).map(name => unsafeWrap(IndexName)(name.toLowerCase)) + def genDocumentId: Gen[Any, DocumentId] = Gen.stringBounded(10, 40)(Gen.alphaNumericChar).map(DocumentId(_)) def genCustomer: Gen[Any, CustomerDocument] = for { diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticConfig.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticConfig.scala index cbcbc32a8..6cd61ec2a 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticConfig.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticConfig.scala @@ -1,6 +1,10 @@ package zio.elasticsearch -final case class ElasticConfig(host: String, port: Int) +import sttp.model.Uri + +final case class ElasticConfig(host: String, port: Int) { + lazy val uri: Uri = Uri(host, port) +} object ElasticConfig { lazy val Default: ElasticConfig = ElasticConfig("localhost", 9200) diff --git a/modules/library/src/main/scala/zio/elasticsearch/HttpElasticExecutor.scala b/modules/library/src/main/scala/zio/elasticsearch/HttpElasticExecutor.scala index 42e42c448..06adeb072 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/HttpElasticExecutor.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/HttpElasticExecutor.scala @@ -4,7 +4,6 @@ import sttp.client3.ziojson._ import sttp.client3.{Identity, RequestT, Response, ResponseException, SttpBackend, UriContext, basicRequest => request} import sttp.model.MediaType.ApplicationJson import sttp.model.StatusCode.Ok -import sttp.model.Uri import zio.Task import zio.ZIO.logDebug import zio.elasticsearch.ElasticRequest._ @@ -14,8 +13,6 @@ private[elasticsearch] final class HttpElasticExecutor private (config: ElasticC import HttpElasticExecutor._ - private val basePath = Uri(config.host, config.port) - override def execute[A](request: ElasticRequest[A]): Task[A] = request match { case r: Create => executeCreate(r) @@ -29,7 +26,7 @@ private[elasticsearch] final class HttpElasticExecutor private (config: ElasticC } private def executeGetById(r: GetById): Task[Option[Document]] = { - val uri = uri"$basePath/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) + val uri = uri"${config.uri}/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) sendRequestWithCustomResponse[ElasticGetResponse]( request @@ -41,9 +38,9 @@ private[elasticsearch] final class HttpElasticExecutor private (config: ElasticC private def executeCreate(r: Create): Task[Option[DocumentId]] = { val uri = r.id match { case Some(documentId) => - uri"$basePath/${r.index}/$Create/$documentId".withParam("routing", r.routing.map(Routing.unwrap)) + uri"${config.uri}/${r.index}/$Create/$documentId".withParam("routing", r.routing.map(Routing.unwrap)) case None => - uri"$basePath/${r.index}/$Doc".withParam("routing", r.routing.map(Routing.unwrap)) + uri"${config.uri}/${r.index}/$Doc".withParam("routing", r.routing.map(Routing.unwrap)) } sendRequestWithCustomResponse[ElasticCreateResponse]( @@ -58,28 +55,28 @@ private[elasticsearch] final class HttpElasticExecutor private (config: ElasticC private def executeCreateIndex(createIndex: CreateIndex): Task[Unit] = sendRequest( request - .put(uri"$basePath/${createIndex.name}") + .put(uri"${config.uri}/${createIndex.name}") .contentType(ApplicationJson) .body(createIndex.definition.getOrElse("")) ).unit private def executeCreateOrUpdate(r: CreateOrUpdate): Task[Unit] = { - val uri = uri"$basePath/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) + val uri = uri"${config.uri}/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) sendRequest(request.put(uri).contentType(ApplicationJson).body(r.document.json)).unit } private def executeExists(r: Exists): Task[Boolean] = { - val uri = uri"$basePath/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) + val uri = uri"${config.uri}/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) sendRequest(request.head(uri)).map(_.code.equals(Ok)) } private def executeDeleteIndex(r: DeleteIndex): Task[Unit] = - sendRequest(request.delete(uri"$basePath/${r.name}")).unit + sendRequest(request.delete(uri"${config.uri}/${r.name}")).unit private def executeDeleteById(r: DeleteById): Task[Option[Unit]] = { - val uri = uri"$basePath/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) + val uri = uri"${config.uri}/${r.index}/$Doc/${r.id}".withParam("routing", r.routing.map(Routing.unwrap)) sendRequestWithCustomResponse( request