scalatransactionsscalatestcats-effectskunk

How to rollback a skunk transaction for integration tests? (missing implicit Origin)


I'm writing integration tests of skunk code against postgres using scala-test-containers, munit and munit-cats-effect, using Mill and Scala 3. I'm new to Cats Effect.

The setup I'm working to achieve is:

Below is the code so far, which the compiler is ok with except for the call to rollback, which requires an implicit skunk.util.Origin. I don't see any reference to this type in the skunk docs and I can't figure out how I'm supposed to bring it into scope.

How can I get the call to rollback to compile in this code? Where is the missing implicit Origin?

import cats.effect.IO
import munit.CatsEffectSuite
import org.testcontainers.utility.DockerImageName
import cats.effect.kernel.Resource
import skunk.*
import skunk.implicits.*
import natchez.Trace.Implicits.noop
import munit.catseffect.IOFixture
import cats.implicits.catsSyntaxFlatMapOps

val container: Resource[IO, PostgreSQLContainer] =
  Resource.make
    (for {
      c <- IO(PostgreSQLContainer(
        DockerImageName.parse("postgres:17-alpine")
      ))
      _ <- IO.blocking(c.start())
    } yield c)
    (c => IO.blocking(c.stop()))

val sessionResource: Resource[IO, Session[IO]] =
  container.flatMap { c =>
    Session.single[IO](
      host = "localhost",
      port = 5432,
      user = c.username,
      password = Some(c.password),
      database = c.databaseName
    )
  }

def useWithRollback[T](s: Session[IO])(f: Transaction[IO] => IO[T]) =
  s.transaction.use { xa =>
    for {
      result <- f(xa)
      _ <- xa.rollback()  // fails compilation: needs implicit skunk.util.Origin
    } yield result
  }

class PgIntegrationTests extends CatsEffectSuite:

  val session = ResourceSuiteLocalFixture(
    "skunk session",
    sessionResource
  )

  override def munitFixtures = List(session)

  def withDb[T](f: Transaction[IO] => IO[T]) =
    useWithRollback(session())(f)

The idea is that a test would look something like

  test("something") {
    withDb { xa =>
      for {
        result <- callMyCode(xa)
        _ <- assertIO(result, <expected result>)
      } yield ()
    }
  }

The above fails compilation, in useWithRollback with

[error] 41 |      _ <- xa.rollback()
[error]    |           ^^^^^^^^^^^
[error]    |           None of the overloaded alternatives of method rollback in trait Transaction with types
[error]    |            (implicit o: skunk.util.Origin): cats.effect.IO[skunk.data.Completion]
[error]    |            (savepoint: xa.Savepoint)(implicit o: skunk.util.Origin): cats.effect.IO[skunk.data.Completion]
[error]    |           match arguments ()

from which I gather it's looking for an Origin in scope. How can I bring that Origin into scope? Why doesn't the skunk Transaction page (linked above) have this issue where it calls rollback in its example?

Thanks for your time.

P.S: The Mill build file build.sc:

import mill._, scalalib._

object devmap extends ScalaModule {
  def scalaVersion = "3.5.1"
  def ivyDeps = Agg(
    ivy"org.tpolecat::skunk-core:0.6.4",
    ivy"org.typelevel::cats-effect:3.5.7"
  )

  object test extends ScalaTests with TestModule.Munit {
    def ivyDeps = Agg(
      ivy"org.scalameta::munit::0.7.29",
      ivy"org.typelevel::munit-cats-effect:2.0.0",
      ivy"com.dimafeng::testcontainers-scala-postgresql:0.41.5",
      ivy"org.postgresql:postgresql:42.7.4",
      ivy"com.lihaoyi::pprint:0.9.0"
    )
  }
}

Solution

  • Try with this:

    def useWithRollback[T](s: Session[IO])(f: Transaction[IO] => IO[T]) =
      s.transaction.use { xa =>
        for {
          sp <- xa.savepoint
          result <- f(xa)
          _ <- xa.rollback(sp)
        } yield result
      }