Skip to content
This repository has been archived by the owner on Dec 1, 2024. It is now read-only.

Commit

Permalink
README + example
Browse files Browse the repository at this point in the history
  • Loading branch information
jjba23 committed Oct 30, 2024
1 parent 63ab33d commit f77e6d2
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 15 deletions.
37 changes: 22 additions & 15 deletions README.org
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
* ZZSpec
#+title: ZZSpec
#+author: Josep Bigorra
#+email: jjbigorra@gmail.com
#+options: num:nil


#+begin_html
<div>
Expand All @@ -10,9 +14,9 @@
A Scala library with great ZIO integration to help you easily write high-level integration/black box tests.
~zzspec~ helps you grow your confidence in the correctness of software with ZIO (test).

*Why ?*
** Why?

Testing at a high level means freedom of implementation and refactoring, this is something all engineers love.
Testing at a high level means freedom of implementation and refactoring, this is something all engineers love.
Also, we love functional programming, Scala and ZIO ❤️.

/Tests should be the compass to study and prove the correctness of the behaviour of a system/.
Expand Down Expand Up @@ -54,6 +58,8 @@ See the Kafka spec [[./zzspec/src/test/scala/kafkatest/KafkaSpec.scala][Kafka sp

See the MockServer spec [[./zzspec/src/test/scala/mockservertest/MockServerSpec.scala][MockServer here]].

See an example "real world" test which combines MockServer and PostgreSQL here, with prepared DB state and post-run DB checks:
[[file:docs/examples/real-world-postgre-and-mockserver.scala]].

** Installing

Expand Down Expand Up @@ -117,34 +123,36 @@ Testers require less technical knowledge, programming or IT skills and do not ne
More loose coupling from the code means more freedom of implementation + refactor


* Contributing
** Contributing

Please feel free to open a pull request, GitHub issue or reach out to me personally (Joe - jjbigorra@gmail.com).

By contributing, your work will be protected under the GNU Lesser General Public License v3.0.


* Project management - Backlog
** Project management - Backlog

*** TODO Developing benchmarking capabilities (HTTP, Kafka, IO, Elastic, PostgreSQL)

** Developing benchmarking capabilities (HTTP, Kafka, IO, Elastic, PostgreSQL)
*** TODO Add Kafka and Postgres windowing test capabilities

** Use more capabilities of ZIO test and its data generators
*** TODO Use more capabilities of ZIO test and its data generators

** Make container layers more customizable and configurable
*** TODO Make container layers more customizable and configurable

** Add Kafka Schema Registry container and Protobuf testing facilities
*** TODO Add Kafka Schema Registry container and Protobuf testing facilities

** Auto-generate and publish Scaladoc and documentation in Github Pages (with CI)
*** TODO Auto-generate and publish Scaladoc and documentation in Github Pages (with CI)

** Allow "initial state" in PostgreSQL and in Opensearch more easily
*** TODO Allow "initial state" in PostgreSQL and in Opensearch more easily

** Considering not using Jackson to work with JSON
*** TODO Considering not using Jackson to work with JSON

** Auto-tag and publish artifacts to Maven (with CI)
*** TODO Auto-tag and publish artifacts to Maven (with CI)
~sbt publishSigned~ -> ~sbt sonatypeBundleRelease~


* ✅ Work done
** ✅ Work done

*** DONE Move to Slick instead of Scalikejdbc
CLOSED: <2024-09-24 di>
Expand All @@ -155,4 +163,3 @@ CLOSED: [2024-09-16 ma 00:06]
*** DONE Add vision statement and improve README
CLOSED: [2024-09-16 ma 00:07]


208 changes: 208 additions & 0 deletions docs/examples/real-world-postgre-and-mockserver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package com.example.integrationtest

import io.github.jjba23.zzspec.ZZSpec
import io.github.jjba23.zzspec.ZZSpec.{ containerLogger, networkLayer }
import io.github.jjba23.zzspec.mockserver._
import io.github.jjba23.zzspec.postgresql._
import zio._
import zio.test._
import java.util.UUID
import com.example.domain.SomeRepository
import com.example.persistence.PostgreSQLSomeRepository
import slick.jdbc.JdbcBackend
import com.example.domain.Some
import com.example.persistence.Flyway
import org.mockserver.mock.Expectation

object BlackBoxSpec extends ZIOSpecDefault {
def spec: Spec[Any, Throwable] =
suite("Some Job - BlackBox specs")(
happyFlowEmptyListSpec,
happyFlowListOf2Spec,
happyFlowListOf1OneAlreadyExistsSpec,
) @@ TestAspect.withLiveConsole @@ TestAspect.sequential // use parallel if enough horsepower

private val keycloakReturnsTokenRestBehaviour: ZIO[MockServer.Client with MockServer.Client, Throwable, Array[Expectation]] =
ZIO.serviceWithZIO[MockServer.Client](mockServer =>
ZIO.attempt(
mockServer.value.when(org.mockserver.model.HttpRequest.request().withPath("/realms/some/protocol/openid-connect/token")).respond(
org.mockserver.model.HttpResponse.response()
.withBody(""" {"access_token": "some-super-secret-token"} """)
.withStatusCode(200),
),
),
)

private val prometheusPushingReturnsOkRestBehaviour: ZIO[MockServer.Client with MockServer.Client, Throwable, Array[Expectation]] =
ZIO.serviceWithZIO[MockServer.Client](mockServer =>
ZIO.attempt(
mockServer.value.when(
org.mockserver.model.HttpRequest.request().withPath("/metrics/job/someJob"),
).respond(org.mockserver.model.HttpResponse.response().withStatusCode(200)),
),
)

private def happyFlowEmptyListSpec: Spec[Any, Throwable] = cssofjBlackBoxTest(
specName = "Happy flow empty list of onboarding failures returned",
mockServerBehaviour = ZIO.serviceWithZIO[MockServer.Client](mockServer =>
keycloakReturnsTokenRestBehaviour *> prometheusPushingReturnsOkRestBehaviour
*> ZIO.attempt(
mockServer.value.when(org.mockserver.model.HttpRequest.request().withPath("/api/v1/some-endpoint"))
.respond(
org.mockserver.model.HttpResponse.response().withStatusCode(200)
.withBody("""
{
"httpStatusCode": 200,
"timestamp": "1970-01-01T00:00:00Z",
"data": {
"someData":[]
}
}
"""),
),
),
),
)

private def happyFlowListOf2Spec: Spec[Any, Throwable] = cssofjBlackBoxTest(
specName = "Happy flow list of 2 onboarding failures returned",
mockServerBehaviour =
keycloakReturnsTokenRestBehaviour *> prometheusPushingReturnsOkRestBehaviour *>
ZIO.serviceWithZIO[MockServer.Client](mockServer =>
ZIO.attempt(
mockServer.value.when(org.mockserver.model.HttpRequest.request().withPath("/api/v1/some-endpoint"))
.respond(org.mockserver.model.HttpResponse.response().withStatusCode(200).withBody("""
{
"httpStatusCode": 200,
"timestamp": "1970-01-01T00:00:00Z",
"data": {
"someData": [
{ "id": "7104b44c-c7d3-499d-aed3-7df074f37ed8",
"creationTime": "1970-01-01T00:00:00Z"
},
{ "id": "a8163ec8-3b07-44a5-8390-51267622c879",
"creationTime": "1970-01-01T00:00:00Z"
}
]
}
}
""")),
),
),
postgreSQLChecks =
for {
repo <- ZIO.service[SomeRepository]
maybeFirstRow <- repo.getByIds(Set(UUID.fromString("7104b44c-c7d3-499d-aed3-7df074f37ed8")))
maybeSecondRow <- repo.getByIds(Set(UUID.fromString("a8163ec8-3b07-44a5-8390-51267622c879")))
nonAlerted <- repo.getNonAlerted.map(_.map(_.map(_.id)))

} yield assertTrue(
maybeFirstRow.map(_.headOption.map(_.id)) ==
Right(Some(UUID.fromString("7104b44c-c7d3-499d-aed3-7df074f37ed8"))),
maybeSecondRow.map(_.headOption.map(_.id)) ==
Right(Some(UUID.fromString("a8163ec8-3b07-44a5-8390-51267622c879"))),
),
)

private def happyFlowListOf1OneAlreadyExistsSpec: Spec[Any, Throwable] = cssofjBlackBoxTest(
specName = "Happy flow list of 1 onboarding failures returned, one non-alerted was already present in DB",
mockServerBehaviour = ZIO.serviceWithZIO[MockServer.Client](mockServer =>
keycloakReturnsTokenRestBehaviour *> prometheusPushingReturnsOkRestBehaviour *> ZIO.attempt(
mockServer.value.when(org.mockserver.model.HttpRequest.request().withPath("/api/v1/onboarding-failures"))
.respond(org.mockserver.model.HttpResponse.response().withStatusCode(200).withBody("""
{
"httpStatusCode": 200,
"timestamp": "1970-01-01T00:00:00Z",
"data": {
"someData": [
{ "id": "7104b44c-c7d3-499d-aed3-7df074f37ed8",
"creationTime": "1970-01-01T00:00:00Z"
},
{ "id": "a8163ec8-3b07-44a5-8390-51267622c879",
"creationTime": "1970-01-01T00:00:00Z"
}
]
}
}
""")),
),
),
postgreSQLChecks =
for {
repo <- ZIO.service[SomeRepository]
maybeFirstRow <- repo.getByIds(Set(UUID.fromString("7104b44c-c7d3-499d-aed3-7df074f37ed8")))
maybeSecondRow <- repo.getByIds(Set(UUID.fromString("a8163ec8-3b07-44a5-8390-51267622c879")))
nonAlerted <- repo.getNonAlerted.map(_.map(_.map(_.id)))
} yield assertTrue(
maybeFirstRow.map(_.headOption.map(_.id)) ==
Right(Some(UUID.fromString("7104b44c-c7d3-499d-aed3-7df074f37ed8"))),
maybeSecondRow.map(_.headOption.map(_.id)) ==
Right(Some(UUID.fromString("a8163ec8-3b07-44a5-8390-51267622c879"))),
),
postgreSQLState = for {
_ <- Flyway.migrate()
repo <- ZIO.service[SomeRepository]
now <- Clock.instant
insertRes <- repo.save(Seq(Some(
id = UUID.fromString("7104b44c-c7d3-499d-aed3-7df074f37ed8"),
creationTime = java.time.Instant.EPOCH
)))
_ <- ZIO.logInfo(insertRes.toString())
} yield (),
)

private def cssofjBlackBoxTest(
specName: String,
mockServerBehaviour: ZIO[MockServer.Client, Throwable, Unit],
postgreSQLChecks: ZIO[JdbcBackend.Database with SomeRepository, Throwable, TestResult] =
assertTrue(true),
postgreSQLState: ZIO[JdbcBackend.Database with SomeRepository, Throwable, Unit] = ZIO.unit,
): Spec[Any, Throwable] =
test(specName) {
for {
postgreSQL <- ZIO.service[PostgreSQLContainer.Container]
mockServer <- ZIO.service[MockServer.Client]

_ <- ZIO.logInfo("preparing PostgreSQL state")
_ <- postgreSQLState

_ <- ZIO.logInfo("preparing MockServer behaviour")
_ <- mockServerBehaviour

_ <- ZIO.logInfo("starting an sbt module run")
// effectively doing: sbt "someJob/run"
_ <- ZZSpec.runSbtModule(ZZSpec.SbtModuleRun(
sbtTask = "run",
moduleName = Some("someJob"),
env = Map(
(
"POSTGRES_JDBC_URL",
s"jdbc:postgresql://${postgreSQL.value.getHost()}:${postgreSQL.value.getMappedPort(5432)}/${postgreSQL.value.getDatabaseName()}?search_path=some_job",
),
("POSTGRES_USER", postgreSQL.value.getUsername()),
("POSTGRES_PASSWORD", postgreSQL.value.getPassword()),
("PUSHGATEWAY_HOST", mockServer.value.remoteAddress().getHostString()),
("PUSHGATEWAY_PORT", mockServer.value.getPort().toString()),
(
"SOME_SERVICE_URL",
s"http://${mockServer.value.remoteAddress().getHostString()}:${mockServer.value.getPort()}",
),
("KEYCLOAK_AUTH_SERVER_URL", s"http://${mockServer.value.remoteAddress().getHostString()}:${mockServer.value.getPort()}"),
),
testLocation = "./",
))

_ <- ZIO.logInfo("performing PostgreSQL checks")
persistenceChecks <- postgreSQLChecks
} yield assertTrue(true) && persistenceChecks
}.provide(
networkLayer,
containerLogger(),
PostgreSQLContainer.Settings.layer(),
PostgreSQLContainer.layer(),
MockServerContainer.layer(),
MockServer.layer,
PostgreSQLPool.layer(schema = "some_job"),
PostgreSQLSomeRepository.layer,
) @@ TestAspect.tag("blackbox")
}

0 comments on commit f77e6d2

Please sign in to comment.