diff --git a/distage-example-bifunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala b/distage-example-bifunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala index 4de1706..d4b5ca3 100644 --- a/distage-example-bifunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala +++ b/distage-example-bifunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala @@ -1,32 +1,37 @@ package leaderboard.plugins -import distage.Scene +import distage.{ModuleDef, Scene} import izumi.distage.docker.Docker.DockerPort import izumi.distage.docker.bundled.PostgresDocker import izumi.distage.docker.modules.DockerSupportModule import izumi.distage.plugins.PluginDef +import izumi.reflect.TagKK import leaderboard.config.PostgresPortCfg -import zio.Task +import zio.IO object PostgresDockerPlugin extends PluginDef { - // only enable postgres docker when Scene axis is set to Managed - tag(Scene.Managed) + include(dockerModule[IO]) - // add docker support dependencies - include(DockerSupportModule[Task]) + def dockerModule[F[+_, +_]: TagKK]: ModuleDef = new ModuleDef { + // only enable postgres docker when Scene axis is set to Managed + tag(Scene.Managed) - // launch postgres docker for tests - make[PostgresDocker.Container] - .fromResource(PostgresDocker.make[Task]) + // add docker support dependencies + include(DockerSupportModule[F[Throwable, _]]) - // spawned docker container port is randomized - // to prevent conflicts, so make PostgresPortCfg - // point to the new port. This will also - // cause the container to start before - // integration check is performed - make[PostgresPortCfg].from { - (docker: PostgresDocker.Container) => - val knownAddress = docker.availablePorts.first(DockerPort.TCP(5432)) - PostgresPortCfg(knownAddress.hostString, knownAddress.port) + // launch postgres docker for tests + make[PostgresDocker.Container] + .fromResource(PostgresDocker.make[F[Throwable, _]]) + + // spawned docker container port is randomized + // to prevent conflicts, so make PostgresPortCfg + // point to the new port. This will also + // cause the container to start before + // integration check is performed + make[PostgresPortCfg].from { + (docker: PostgresDocker.Container) => + val knownAddress = docker.availablePorts.first(DockerPort.TCP(5432)) + PostgresPortCfg(knownAddress.hostString, knownAddress.port) + } } } diff --git a/distage-example-monofunctor-tf/src/main/scala/leaderboard/LeaderboardRole.scala b/distage-example-monofunctor-tf/src/main/scala/leaderboard/LeaderboardRole.scala index fa897cd..bf77a7d 100644 --- a/distage-example-monofunctor-tf/src/main/scala/leaderboard/LeaderboardRole.scala +++ b/distage-example-monofunctor-tf/src/main/scala/leaderboard/LeaderboardRole.scala @@ -1,21 +1,22 @@ package leaderboard import cats.Applicative +import cats.effect.Async import distage.StandardAxis.Repo import distage.plugins.PluginConfig import distage.{Activation, Lifecycle, Module, ModuleDef} import izumi.distage.model.definition.StandardAxis.Scene +import izumi.distage.modules.DefaultModule import izumi.distage.roles.RoleAppMain import izumi.distage.roles.bundled.{ConfigWriter, Help} import izumi.distage.roles.model.{RoleDescriptor, RoleService} -import izumi.fundamentals.platform.IzPlatform import izumi.fundamentals.platform.cli.model.raw.{RawEntrypointParams, RawRoleParams, RawValue} +import izumi.reflect.TagK import logstage.LogIO import leaderboard.api.{LadderApi, ProfileApi} import leaderboard.http.HttpServer import leaderboard.plugins.{LeaderboardPlugin, PostgresDockerPlugin} import zio.interop.catz.asyncInstance -import zio.Task import scala.annotation.unused @@ -119,7 +120,8 @@ object LeaderboardRole extends RoleDescriptor { * ./launcher -u repo:dummy :leaderboard * }}} */ -object MainDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LeaderboardRole.id))) +object MainDummyCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LeaderboardRole.id))) +object MainDummyZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LeaderboardRole.id))) /** * Launch with production configuration and setup the required postgres DB inside docker. @@ -131,7 +133,8 @@ object MainDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector(RawRole * ./launcher -u scene:managed :leaderboard * }}} */ -object MainProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LeaderboardRole.id))) +object MainProdDockerCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LeaderboardRole.id))) +object MainProdDockerZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LeaderboardRole.id))) /** * Launch with production configuration and external, not dockerized, services. @@ -148,7 +151,8 @@ object MainProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Sc * ./launcher :leaderboard * }}} */ -object MainProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LeaderboardRole.id))) +object MainProdCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LeaderboardRole.id))) +object MainProdZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LeaderboardRole.id))) /** * Launch just the `ladder` APIs with dummy repositories @@ -158,7 +162,8 @@ object MainProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Pr * ./launcher -u repo:dummy :ladder * }}} */ -object MainLadderDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LadderRole.id))) +object MainLadderDummyCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LadderRole.id))) +object MainLadderDummyZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(LadderRole.id))) /** * Launch just the `ladder` APIs with postgres repositories and dockerized postgres service @@ -168,7 +173,8 @@ object MainLadderDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector(R * ./launcher -u scene:managed :ladder * }}} */ -object MainLadderProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LadderRole.id))) +object MainLadderProdDockerCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LadderRole.id))) +object MainLadderProdDockerZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(LadderRole.id))) /** * Launch just the `ladder` APIs with postgres repositories and external postgres service @@ -180,7 +186,8 @@ object MainLadderProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scene * ./launcher :ladder * }}} */ -object MainLadderProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LadderRole.id))) +object MainLadderProdCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LadderRole.id))) +object MainLadderProdZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(LadderRole.id))) /** * Launch just the `profile` APIs with dummy repositories @@ -190,7 +197,8 @@ object MainLadderProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Sc * ./launcher -u repo:dummy :profile * }}} */ -object MainProfileDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileDummyCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileDummyZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Dummy), Vector(RawRoleParams(ProfileRole.id))) /** * Launch just the `profile` APIs with postgres repositories and dockerized postgres service @@ -200,7 +208,8 @@ object MainProfileDummy extends MainBase(Activation(Repo -> Repo.Dummy), Vector( * ./launcher -u scene:managed :profile * }}} */ -object MainProfileProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileProdDockerCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileProdDockerZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Managed), Vector(RawRoleParams(ProfileRole.id))) /** * Launch just the `profile` APIs with postgres repositories and external postgres service @@ -210,7 +219,8 @@ object MainProfileProdDocker extends MainBase(Activation(Repo -> Repo.Prod, Scen * ./launcher :profile * }}} */ -object MainProfileProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileProdCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(ProfileRole.id))) +object MainProfileProdZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(ProfileRole.id))) /** * Display help message with all available launcher arguments @@ -221,7 +231,8 @@ object MainProfileProd extends MainBase(Activation(Repo -> Repo.Prod, Scene -> S * ./launcher :help * }}} */ -object MainHelp extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(Help.id))) +object MainHelpCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(Help.id))) +object MainHelpZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector(RawRoleParams(Help.id))) /** * Write the default configuration files for each role into JSON files in `./config`. @@ -236,8 +247,10 @@ object MainHelp extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Pr * ./launcher :configwriter * }}} */ -object MainWriteReferenceConfigs - extends MainBase( +object MainWriteReferenceConfigsCats extends MainWriteReferenceConfigsBase[cats.effect.IO] +object MainWriteReferenceConfigsZIO extends MainWriteReferenceConfigsBase[zio.Task] +abstract class MainWriteReferenceConfigsBase[F[_]: TagK: Async: DefaultModule] + extends MainBase[F]( activation = { Activation(Repo -> Repo.Prod, Scene -> Scene.Provided) }, @@ -288,26 +301,23 @@ object MainWriteReferenceConfigs * * }}} */ -object GenericLauncher extends MainBase(Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector.empty) +object GenericLauncherCats extends MainBase[cats.effect.IO](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector.empty) +object GenericLauncherZIO extends MainBase[zio.Task](Activation(Repo -> Repo.Prod, Scene -> Scene.Provided), Vector.empty) -sealed abstract class MainBase( +sealed abstract class MainBase[F[_]: TagK: Async: DefaultModule]( activation: Activation, requiredRoles: Vector[RawRoleParams], -) extends RoleAppMain.LauncherCats[Task] { +) extends RoleAppMain.LauncherCats[F] { override def requiredRoles(argv: RoleAppMain.ArgV): Vector[RawRoleParams] = { requiredRoles } - override def pluginConfig: PluginConfig = { - if (IzPlatform.isGraalNativeImage) { - // Only this would work reliably for NativeImage - PluginConfig.const(List(LeaderboardPlugin, PostgresDockerPlugin)) - } else { - // Runtime discovery with PluginConfig.cached might be convenient for pure jvm projects during active development - // Once the project gets to the maintenance stage it's a good idea to switch to PluginConfig.const - PluginConfig.cached(pluginsPackage = "leaderboard.plugins") - } + override val pluginConfig: PluginConfig = { + // When our plugins are polymorphic, we can't use runtime discovery, + // we must instantiate them manually using PluginConfig.const. + // Also, only manual instantiation works reliably with NativeImage. + PluginConfig.const(List(LeaderboardPlugin[F], PostgresDockerPlugin[F])) } protected override def roleAppBootOverrides(argv: RoleAppMain.ArgV): Module = super.roleAppBootOverrides(argv) ++ new ModuleDef { diff --git a/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/LeaderboardPlugin.scala b/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/LeaderboardPlugin.scala index 921a131..ea05b1f 100644 --- a/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/LeaderboardPlugin.scala +++ b/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/LeaderboardPlugin.scala @@ -4,11 +4,10 @@ import distage.StandardAxis.Repo import distage.config.ConfigModuleDef import distage.{ModuleDef, Scene, TagK} import doobie.util.transactor.Transactor -import izumi.distage.plugins.PluginDef +import izumi.distage.plugins.{PluginBase, PluginDef} import izumi.distage.roles.bundled.BundledRolesModule import izumi.distage.roles.model.definition.RoleModuleDef import izumi.fundamentals.platform.integration.PortCheck -import zio.Task import leaderboard.api.{HttpApi, LadderApi, ProfileApi} import leaderboard.config.{PostgresCfg, PostgresPortCfg} import leaderboard.http.HttpServer @@ -20,13 +19,15 @@ import org.http4s.dsl.Http4sDsl import scala.concurrent.duration.* -object LeaderboardPlugin extends PluginDef { - include(modules.roles[Task]) - include(modules.api[Task]) - include(modules.repoDummy[Task]) - include(modules.repoProd[Task]) - include(modules.configs) - include(modules.prodConfigs) +object LeaderboardPlugin { + def apply[F[_]: TagK]: PluginBase = new PluginDef { + include(modules.roles) + include(modules.api) + include(modules.repoDummy) + include(modules.repoProd) + include(modules.configs) + include(modules.prodConfigs) + } object modules { def roles[F[_]: TagK]: RoleModuleDef = new RoleModuleDef { diff --git a/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala b/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala index 4de1706..5bbd390 100644 --- a/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala +++ b/distage-example-monofunctor-tf/src/main/scala/leaderboard/plugins/PostgresDockerPlugin.scala @@ -1,32 +1,38 @@ package leaderboard.plugins -import distage.Scene +import distage.{ModuleDef, Scene} import izumi.distage.docker.Docker.DockerPort import izumi.distage.docker.bundled.PostgresDocker import izumi.distage.docker.modules.DockerSupportModule -import izumi.distage.plugins.PluginDef +import izumi.distage.plugins.{PluginBase, PluginDef} +import izumi.reflect.TagK import leaderboard.config.PostgresPortCfg -import zio.Task -object PostgresDockerPlugin extends PluginDef { - // only enable postgres docker when Scene axis is set to Managed - tag(Scene.Managed) +object PostgresDockerPlugin { + def apply[F[_]: TagK]: PluginBase = new PluginDef { + include(dockerModule[F]) + } + + def dockerModule[F[_]: TagK]: ModuleDef = new ModuleDef { + // only enable postgres docker when Scene axis is set to Managed + tag(Scene.Managed) - // add docker support dependencies - include(DockerSupportModule[Task]) + // add docker support dependencies + include(DockerSupportModule[F]) - // launch postgres docker for tests - make[PostgresDocker.Container] - .fromResource(PostgresDocker.make[Task]) + // launch postgres docker for tests + make[PostgresDocker.Container] + .fromResource(PostgresDocker.make[F]) - // spawned docker container port is randomized - // to prevent conflicts, so make PostgresPortCfg - // point to the new port. This will also - // cause the container to start before - // integration check is performed - make[PostgresPortCfg].from { - (docker: PostgresDocker.Container) => - val knownAddress = docker.availablePorts.first(DockerPort.TCP(5432)) - PostgresPortCfg(knownAddress.hostString, knownAddress.port) + // spawned docker container port is randomized + // to prevent conflicts, so make PostgresPortCfg + // point to the new port. This will also + // cause the container to start before + // integration check is performed + make[PostgresPortCfg].from { + (docker: PostgresDocker.Container) => + val knownAddress = docker.availablePorts.first(DockerPort.TCP(5432)) + PostgresPortCfg(knownAddress.hostString, knownAddress.port) + } } } diff --git a/distage-example-monofunctor-tf/src/test/scala/leaderboard/WiringTest.scala b/distage-example-monofunctor-tf/src/test/scala/leaderboard/WiringTest.scala index 8d3d701..2cb1b16 100644 --- a/distage-example-monofunctor-tf/src/test/scala/leaderboard/WiringTest.scala +++ b/distage-example-monofunctor-tf/src/test/scala/leaderboard/WiringTest.scala @@ -2,4 +2,5 @@ package leaderboard import izumi.distage.testkit.scalatest.SpecWiring -final class WiringTest extends SpecWiring(GenericLauncher) +final class WiringTestZIO extends SpecWiring(GenericLauncherZIO) +final class WiringTestCats extends SpecWiring(GenericLauncherCats) diff --git a/distage-example-monofunctor-tf/src/test/scala/leaderboard/tests.scala b/distage-example-monofunctor-tf/src/test/scala/leaderboard/tests.scala index b9098d9..3223c6b 100644 --- a/distage-example-monofunctor-tf/src/test/scala/leaderboard/tests.scala +++ b/distage-example-monofunctor-tf/src/test/scala/leaderboard/tests.scala @@ -1,62 +1,87 @@ package leaderboard -import distage.{DIKey, ModuleDef, Scene} +import cats.Applicative +import cats.effect.Sync +import cats.syntax.all.* +import distage.{DIKey, DefaultModule, ModuleDef, Scene} import izumi.distage.model.definition.Activation import izumi.distage.model.definition.StandardAxis.Repo -import izumi.distage.plugins.PluginConfig -import izumi.distage.testkit.scalatest.{AssertZIO, SpecZIO} +import izumi.distage.testkit.scalatest.{AssertSync, Spec1} +import izumi.distage.testkit.spec.TestConfiguration +import izumi.reflect.TagK import leaderboard.model.* import leaderboard.repo.{Ladder, Profiles} import leaderboard.services.Ranks -import leaderboard.zioenv.* -import zio.{RIO, Task, ZIO} +import zio.interop.catz.asyncInstance -abstract class LeaderboardTest extends SpecZIO with AssertZIO { +abstract class LeaderboardTest[F[_]: TagK: DefaultModule] extends Spec1[F] with AssertSync[F] { override def config = super.config.copy( - pluginConfig = PluginConfig.cached(packagesEnabled = Seq("leaderboard.plugins")), moduleOverrides = super.config.moduleOverrides ++ new ModuleDef { - make[Rnd[Task]].from[Rnd.Impl[Task]] + make[Rnd[F]].from[Rnd.Impl[F]] }, - // For testing, setup a docker container with postgres, + // For testing, set up a docker container with postgres, // instead of trying to connect to an external database activation = Activation(Scene -> Scene.Managed), // Instantiate Ladder & Profiles only once per test-run and // share them and all their dependencies across all tests. // this includes the Postgres Docker container above and table DDLs memoizationRoots = Set( - DIKey[Ladder[Task]], - DIKey[Profiles[Task]], + DIKey[Ladder[F]], + DIKey[Profiles[F]], ), ) } -trait DummyTest extends LeaderboardTest { - override final def config = super.config.copy( +trait DummyTest extends TestConfiguration { + abstract override def config = super.config.copy( activation = super.config.activation ++ Activation(Repo -> Repo.Dummy) ) } -trait ProdTest extends LeaderboardTest { - override final def config = super.config.copy( +trait ProdTest extends TestConfiguration { + abstract override def config = super.config.copy( activation = super.config.activation ++ Activation(Repo -> Repo.Prod) ) } -final class LadderTestDummy extends LadderTest with DummyTest -final class ProfilesTestDummy extends ProfilesTest with DummyTest -final class RanksTestDummy extends RanksTest with DummyTest +trait CatsTest extends LeaderboardTest[cats.effect.IO] { + abstract override def config = super.config.copy( + // For memoization across test suites to work predictably, PluginDef + // instances used in pluginConfig should be the same in all the test suites. + // We use pluginConfig from a global val to ensure that instances are shared. + pluginConfig = GenericLauncherCats.pluginConfig + ) +} + +trait ZIOTest extends LeaderboardTest[zio.Task] { + abstract override def config = super.config.copy( + pluginConfig = GenericLauncherZIO.pluginConfig + ) +} + +final class LadderTestDummyCats extends LadderTest[cats.effect.IO] with DummyTest with CatsTest +final class ProfilesTestDummyCats extends ProfilesTest[cats.effect.IO] with DummyTest with CatsTest +final class RanksTestDummyCats extends RanksTest[cats.effect.IO] with DummyTest with CatsTest -final class LadderTestPostgres extends LadderTest with ProdTest -final class ProfilesTestPostgres extends ProfilesTest with ProdTest -final class RanksTestPostgres extends RanksTest with ProdTest +final class LadderTestPostgresCats extends LadderTest[cats.effect.IO] with ProdTest with CatsTest +final class ProfilesTestPostgresCats extends ProfilesTest[cats.effect.IO] with ProdTest with CatsTest +final class RanksTestPostgresCats extends RanksTest[cats.effect.IO] with ProdTest with CatsTest -abstract class LadderTest extends LeaderboardTest { +final class LadderTestDummyZIO extends LadderTest[zio.Task] with DummyTest with ZIOTest +final class ProfilesTestDummyZIO extends ProfilesTest[zio.Task] with DummyTest with ZIOTest +final class RanksTestDummyZIO extends RanksTest[zio.Task] with DummyTest with ZIOTest + +final class LadderTestPostgresZIO extends LadderTest[zio.Task] with ProdTest with ZIOTest +final class ProfilesTestPostgresZIO extends ProfilesTest[zio.Task] with ProdTest with ZIOTest +final class RanksTestPostgresZIO extends RanksTest[zio.Task] with ProdTest with ZIOTest + +abstract class LadderTest[F[_]: Sync: TagK: DefaultModule] extends LeaderboardTest[F] { "Ladder" should { /** this test gets dependencies injected through function arguments */ "submit & get" in { - (rnd: Rnd[Task], ladder: Ladder[Task]) => + (rnd: Rnd[F], ladder: Ladder[F]) => for { user <- rnd[UserId] score <- rnd[Score] @@ -66,64 +91,61 @@ abstract class LadderTest extends LeaderboardTest { } yield () } - /** this test get dependencies injected via ZIO Env: */ "assign a higher position in the list to a higher score" in { - for { - user1 <- rnd[UserId] - score1 <- rnd[Score] - user2 <- rnd[UserId] - score2 <- rnd[Score] - - _ <- ladder.submitScore(user1, score1) - _ <- ladder.submitScore(user2, score2) - scores <- ladder.getScores - - user1Rank = scores.indexWhere(_._1 == user1) - user2Rank = scores.indexWhere(_._1 == user2) - - _ <- - if (score1 > score2) { - assertIO(user1Rank < user2Rank) - } else if (score2 > score1) { - assertIO(user2Rank < user1Rank) - } else ZIO.unit - } yield () + (rnd: Rnd[F], ladder: Ladder[F]) => + for { + user1 <- rnd[UserId] + score1 <- rnd[Score] + user2 <- rnd[UserId] + score2 <- rnd[Score] + + _ <- ladder.submitScore(user1, score1) + _ <- ladder.submitScore(user2, score2) + scores <- ladder.getScores + + user1Rank = scores.indexWhere(_._1 == user1) + user2Rank = scores.indexWhere(_._1 == user2) + + _ <- + if (score1 > score2) { + assertIO(user1Rank < user2Rank) + } else if (score2 > score1) { + assertIO(user2Rank < user1Rank) + } else Applicative[F].unit + } yield () } } } -abstract class ProfilesTest extends LeaderboardTest { +abstract class ProfilesTest[F[_]: Sync: TagK: DefaultModule] extends LeaderboardTest[F] { "Profiles" should { - /** that's what the ZIO signature looks like for ZIO Env injection: */ "set & get" in { - val zioValue: RIO[Profiles[Task] & Rnd[Task], Unit] = for { - user <- rnd[UserId] - name <- rnd[String] - desc <- rnd[String] - profile = UserProfile(name, desc) - _ <- profiles.setProfile(user, profile) - res <- profiles.getProfile(user) - _ <- assertIO(res contains profile) - } yield () - - zioValue + (rnd: Rnd[F], profiles: Profiles[F]) => + for { + user <- rnd[UserId] + name <- rnd[String] + desc <- rnd[String] + profile = UserProfile(name, desc) + _ <- profiles.setProfile(user, profile) + res <- profiles.getProfile(user) + _ <- assertIO(res contains profile) + } yield () } } } -abstract class RanksTest extends LeaderboardTest { +abstract class RanksTest[F[_]: Sync: TagK: DefaultModule] extends LeaderboardTest[F] { "Ranks" should { - /** you can use Argument injection and ZIO Env injection at the same time: */ "return 0 rank for a user with no score" in { - (ranks: Ranks[Task]) => + (rnd: Rnd[F], ranks: Ranks[F], profiles: Profiles[F]) => for { user <- rnd[UserId] name <- rnd[String] @@ -136,43 +158,45 @@ abstract class RanksTest extends LeaderboardTest { } "return None for a user with no profile" in { - for { - user <- rnd[UserId] - score <- rnd[Score] - _ <- ladder.submitScore(user, score) - res1 <- ranks.getRank(user) - _ <- assertIO(res1.isEmpty) - } yield () + (rnd: Rnd[F], ranks: Ranks[F], ladder: Ladder[F]) => + for { + user <- rnd[UserId] + score <- rnd[Score] + _ <- ladder.submitScore(user, score) + res1 <- ranks.getRank(user) + _ <- assertIO(res1.isEmpty) + } yield () } "assign a higher rank to a user with more score" in { - for { - user1 <- rnd[UserId] - name1 <- rnd[String] - desc1 <- rnd[String] - score1 <- rnd[Score] - - user2 <- rnd[UserId] - name2 <- rnd[String] - desc2 <- rnd[String] - score2 <- rnd[Score] - - _ <- profiles.setProfile(user1, UserProfile(name1, desc1)) - _ <- ladder.submitScore(user1, score1) - - _ <- profiles.setProfile(user2, UserProfile(name2, desc2)) - _ <- ladder.submitScore(user2, score2) - - user1Rank <- ranks.getRank(user1).map(_.get.rank) - user2Rank <- ranks.getRank(user2).map(_.get.rank) - - _ <- - if (score1 > score2) { - assertIO(user1Rank < user2Rank) - } else if (score2 > score1) { - assertIO(user2Rank < user1Rank) - } else ZIO.unit - } yield () + (rnd: Rnd[F], ranks: Ranks[F], ladder: Ladder[F], profiles: Profiles[F]) => + for { + user1 <- rnd[UserId] + name1 <- rnd[String] + desc1 <- rnd[String] + score1 <- rnd[Score] + + user2 <- rnd[UserId] + name2 <- rnd[String] + desc2 <- rnd[String] + score2 <- rnd[Score] + + _ <- profiles.setProfile(user1, UserProfile(name1, desc1)) + _ <- ladder.submitScore(user1, score1) + + _ <- profiles.setProfile(user2, UserProfile(name2, desc2)) + _ <- ladder.submitScore(user2, score2) + + user1Rank <- ranks.getRank(user1).map(_.get.rank) + user2Rank <- ranks.getRank(user2).map(_.get.rank) + + _ <- + if (score1 > score2) { + assertIO(user1Rank < user2Rank) + } else if (score2 > score1) { + assertIO(user2Rank < user1Rank) + } else Applicative[F].unit + } yield () } } diff --git a/distage-example-monofunctor-tf/src/test/scala/leaderboard/zioenv.scala b/distage-example-monofunctor-tf/src/test/scala/leaderboard/zioenv.scala deleted file mode 100644 index eb038b3..0000000 --- a/distage-example-monofunctor-tf/src/test/scala/leaderboard/zioenv.scala +++ /dev/null @@ -1,29 +0,0 @@ -package leaderboard - -import leaderboard.model.* -import leaderboard.repo.{Ladder, Profiles} -import leaderboard.services.Ranks -import org.scalacheck.Arbitrary -import zio.{RIO, Task, ZIO} - -object zioenv { - - object ladder extends Ladder[RIO[Ladder[Task], _]] { - def submitScore(userId: UserId, score: Score): RIO[Ladder[Task], Unit] = ZIO.serviceWithZIO(_.submitScore(userId, score)) - def getScores: RIO[Ladder[Task], List[(UserId, Score)]] = ZIO.serviceWithZIO(_.getScores) - } - - object profiles extends Profiles[RIO[Profiles[Task], _]] { - override def setProfile(userId: UserId, profile: UserProfile): RIO[Profiles[Task], Unit] = ZIO.serviceWithZIO(_.setProfile(userId, profile)) - override def getProfile(userId: UserId): RIO[Profiles[Task], Option[UserProfile]] = ZIO.serviceWithZIO(_.getProfile(userId)) - } - - object ranks extends Ranks[RIO[Ranks[Task], _]] { - override def getRank(userId: UserId): RIO[Ranks[Task], Option[RankedProfile]] = ZIO.serviceWithZIO(_.getRank(userId)) - } - - object rnd extends Rnd[RIO[Rnd[Task], _]] { - override def apply[A: Arbitrary]: RIO[Rnd[Task], A] = ZIO.serviceWithZIO(_.apply[A]) - } - -}