~jan0sch/smederee

Showing details for patch 1773d70487ed28ad395f7f0ec5f93ae4ce02421e.
2024-07-28 (Sun), 7:32 PM - Jens Grassel - 1773d70487ed28ad395f7f0ec5f93ae4ce02421e

chore: migrate test to ScalaCheckEffect

Summary of changes
2 files modified with 625 lines added and 661 lines removed
  • modules/hub/src/test/scala/de/smederee/tickets/DoobieTicketRepositoryTest.scala with 621 added and 661 removed lines
  • modules/hub/src/test/scala/de/smederee/tickets/Generators.scala with 4 added and 0 removed lines
diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieTicketRepositoryTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieTicketRepositoryTest.scala
--- old-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieTicketRepositoryTest.scala	2025-01-10 23:53:14.300502869 +0000
+++ new-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieTicketRepositoryTest.scala	2025-01-10 23:53:14.300502869 +0000
@@ -9,16 +9,20 @@
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 
+import cats.data.NonEmptyList
 import cats.effect.*
 import cats.syntax.all.*
 import com.softwaremill.quicklens.*
 import de.smederee.TestTags.*
-import de.smederee.tickets.Generators.*
+import de.smederee.tickets.Generators.given
 import doobie.*
 
+import org.scalacheck.effect.PropF
+
 import scala.collection.immutable.Queue
 
 final class DoobieTicketRepositoryTest extends BaseSpec {
+    override def scalaCheckTestParameters = super.scalaCheckTestParameters.withMinSuccessfulTests(1)
 
     /** Return the internal ids of all lables associated with the given ticket number and project id.
       *
@@ -85,47 +89,45 @@
         }
 
     test("addAssignee must save the assignee relation to the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genTicketsUser.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(user)) =>
-                val assignee = Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString))
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsUser(user)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    _         <- ticket.submitter.traverse(createTicketsSubmitter)
-                    _         <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
-                    _ <- projectId.traverse(projectId => ticketRepo.addAssignee(projectId)(ticket.number)(assignee))
-                    foundAssignees <- projectId.traverse(projectId =>
-                        ticketRepo.loadAssignees(projectId)(ticket.number).compile.toList
-                    )
-                } yield foundAssignees.getOrElse(Nil)
-                test.map { foundAssignees =>
-                    assertEquals(foundAssignees, List(assignee))
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, user: TicketsUser) =>
+            val assignee = Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString))
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsUser(user)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                _         <- ticket.submitter.traverse(createTicketsSubmitter)
+                _         <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
+                _         <- projectId.traverse(projectId => ticketRepo.addAssignee(projectId)(ticket.number)(assignee))
+                foundAssignees <- projectId.traverse(projectId =>
+                    ticketRepo.loadAssignees(projectId)(ticket.number).compile.toList
+                )
+            } yield foundAssignees.getOrElse(Nil)
+            test.start.flatMap(_.joinWithNever).map { foundAssignees =>
+                assertEquals(foundAssignees, List(assignee))
+            }
         }
     }
 
     test("addComment must save comment to the database".tag(NeedsDatabase)) {
-        (
-            genProjectOwner.sample,
-            genProject.sample,
-            genTicket.sample,
-            genTicketsUser.sample,
-            genTicketComment.sample
-        ) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(user), Some(comment)) =>
+        PropF.forAllF {
+            (
+                owner: ProjectOwner,
+                generatedProject: Project,
+                ticket: Ticket,
+                user: TicketsUser,
+                comment: TicketComment
+            ) =>
                 val project  = generatedProject.copy(owner = owner)
                 val dbConfig = configuration.database
                 val tx = Transactor.fromDriverManager[IO](
@@ -149,7 +151,7 @@
                         ticketRepo.loadComments(projectId)(ticket.number).compile.toList
                     )
                 } yield (written.getOrElse(0), foundComments.getOrElse(Nil))
-                test.map { result =>
+                test.start.flatMap(_.joinWithNever).map { result =>
                     val (written, foundComments) = result
                     assert(written > 0, "No rows written to database!")
                     assertEquals(
@@ -157,502 +159,476 @@
                         List(comment)
                     )
                 }
-            case _ => fail("Could not generate data samples!")
         }
     }
 
     test("addLabel must save the label relation to the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(label)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val labelRepo  = new DoobieLabelRepository[IO](tx)
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => fail("Project ID not found in database!")
-                        case Some(projectId) =>
-                            for {
-                                _            <- ticket.submitter.traverse(createTicketsSubmitter)
-                                _            <- labelRepo.createLabel(projectId)(label)
-                                createdLabel <- labelRepo.findLabel(projectId)(label.name)
-                                _            <- ticketRepo.createTicket(projectId)(ticket)
-                                _ <- createdLabel.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
-                                foundLabels <- loadTicketLabelIds(projectId, ticket.number)
-                            } yield (createdLabel, foundLabels)
-                    }
-                } yield result
-                test.map { result =>
-                    val (createdLabel, foundLabels) = result
-                    assert(createdLabel.nonEmpty, "Test label not created!")
-                    createdLabel.flatMap(_.id) match {
-                        case None          => fail("Test label has no ID!")
-                        case Some(labelId) => assert(foundLabels.exists(_ === labelId))
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, label: Label) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val labelRepo  = new DoobieLabelRepository[IO](tx)
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => fail("Project ID not found in database!")
+                    case Some(projectId) =>
+                        for {
+                            _            <- ticket.submitter.traverse(createTicketsSubmitter)
+                            _            <- labelRepo.createLabel(projectId)(label)
+                            createdLabel <- labelRepo.findLabel(projectId)(label.name)
+                            _            <- ticketRepo.createTicket(projectId)(ticket)
+                            _ <- createdLabel.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
+                            foundLabels <- loadTicketLabelIds(projectId, ticket.number)
+                        } yield (createdLabel, foundLabels)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (createdLabel, foundLabels) = result
+                assert(createdLabel.nonEmpty, "Test label not created!")
+                createdLabel.flatMap(_.id) match {
+                    case None          => fail("Test label has no ID!")
+                    case Some(labelId) => assert(foundLabels.exists(_ === labelId))
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("addMilestone must save the milestone relation to the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genMilestone.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(milestone)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val milestoneRepo = new DoobieMilestoneRepository[IO](tx)
-                val ticketRepo    = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => fail("Project ID not found in database!")
-                        case Some(projectId) =>
-                            for {
-                                _                <- ticket.submitter.traverse(createTicketsSubmitter)
-                                _                <- milestoneRepo.createMilestone(projectId)(milestone)
-                                createdMilestone <- milestoneRepo.findMilestone(projectId)(milestone.title)
-                                _                <- ticketRepo.createTicket(projectId)(ticket)
-                                _ <- createdMilestone.traverse(cl =>
-                                    ticketRepo.addMilestone(projectId)(ticket.number)(cl)
-                                )
-                                foundMilestones <- loadTicketMilestoneIds(projectId, ticket.number)
-                            } yield (createdMilestone, foundMilestones)
-                    }
-                } yield result
-                test.map { result =>
-                    val (createdMilestone, foundMilestones) = result
-                    assert(createdMilestone.nonEmpty, "Test milestone not created!")
-                    createdMilestone.flatMap(_.id) match {
-                        case None              => fail("Test milestone has no ID!")
-                        case Some(milestoneId) => assert(foundMilestones.exists(_ === milestoneId))
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, milestone: Milestone) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val milestoneRepo = new DoobieMilestoneRepository[IO](tx)
+            val ticketRepo    = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => fail("Project ID not found in database!")
+                    case Some(projectId) =>
+                        for {
+                            _                <- ticket.submitter.traverse(createTicketsSubmitter)
+                            _                <- milestoneRepo.createMilestone(projectId)(milestone)
+                            createdMilestone <- milestoneRepo.findMilestone(projectId)(milestone.title)
+                            _                <- ticketRepo.createTicket(projectId)(ticket)
+                            _ <- createdMilestone.traverse(cl => ticketRepo.addMilestone(projectId)(ticket.number)(cl))
+                            foundMilestones <- loadTicketMilestoneIds(projectId, ticket.number)
+                        } yield (createdMilestone, foundMilestones)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (createdMilestone, foundMilestones) = result
+                assert(createdMilestone.nonEmpty, "Test milestone not created!")
+                createdMilestone.flatMap(_.id) match {
+                    case None              => fail("Test milestone has no ID!")
+                    case Some(milestoneId) => assert(foundMilestones.exists(_ === milestoneId))
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("allTickets must return all tickets for the project".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(generatedTickets)) =>
-                val defaultTimestamp = OffsetDateTime.now()
-                val tickets =
-                    generatedTickets
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, generatedTickets: NonEmptyList[Ticket]) =>
+            val defaultTimestamp = OffsetDateTime.now()
+            val tickets =
+                generatedTickets.toList
+                    .sortBy(_.number)
+                    .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
+            val submitters = tickets.map(_.submitter).flatten
+            val project    = generatedProject.copy(owner = owner)
+            val dbConfig   = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((0, Nil))
+                    case Some(projectId) =>
+                        for {
+                            writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            foundTickets   <- ticketRepo.allTickets(filter = None)(projectId).compile.toList
+                        } yield (writtenTickets.sum, foundTickets)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (writtenTickets, foundTickets) = result
+                assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
+                assertEquals(
+                    foundTickets.size,
+                    writtenTickets,
+                    "Number of returned tickets differs from number of created tickets!"
+                )
+                assertEquals(
+                    foundTickets
                         .sortBy(_.number)
-                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
-                val submitters = tickets.map(_.submitter).flatten
-                val project    = generatedProject.copy(owner = owner)
-                val dbConfig   = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
+                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
+                    tickets.sortBy(_.number)
                 )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((0, Nil))
-                        case Some(projectId) =>
-                            for {
-                                writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                foundTickets   <- ticketRepo.allTickets(filter = None)(projectId).compile.toList
-                            } yield (writtenTickets.sum, foundTickets)
-                    }
-                } yield result
-                test.map { result =>
-                    val (writtenTickets, foundTickets) = result
-                    assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
-                    assertEquals(
-                        foundTickets.size,
-                        writtenTickets,
-                        "Number of returned tickets differs from number of created tickets!"
-                    )
-                    assertEquals(
-                        foundTickets
-                            .sortBy(_.number)
-                            .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
-                        tickets.sortBy(_.number)
-                    )
-                }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("allTickets must respect given filters for numbers".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(generatedTickets)) =>
-                val defaultTimestamp = OffsetDateTime.now()
-                val tickets =
-                    generatedTickets
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, generatedTickets: NonEmptyList[Ticket]) =>
+            val defaultTimestamp = OffsetDateTime.now()
+            val tickets =
+                generatedTickets.toList
+                    .sortBy(_.number)
+                    .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
+            val expectedTickets = tickets.take(tickets.size / 2)
+            val filter          = TicketFilter(number = expectedTickets.map(_.number), Nil, Nil, Nil)
+            val submitters      = tickets.map(_.submitter).flatten
+            val project         = generatedProject.copy(owner = owner)
+            val dbConfig        = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((0, Nil))
+                    case Some(projectId) =>
+                        for {
+                            writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
+                        } yield (writtenTickets.sum, foundTickets)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (writtenTickets, foundTickets) = result
+                assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
+                assertEquals(
+                    foundTickets
                         .sortBy(_.number)
-                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
-                val expectedTickets = tickets.take(tickets.size / 2)
-                val filter          = TicketFilter(number = expectedTickets.map(_.number), Nil, Nil, Nil)
-                val submitters      = tickets.map(_.submitter).flatten
-                val project         = generatedProject.copy(owner = owner)
-                val dbConfig        = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
+                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
+                    expectedTickets.sortBy(_.number)
                 )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((0, Nil))
-                        case Some(projectId) =>
-                            for {
-                                writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
-                            } yield (writtenTickets.sum, foundTickets)
-                    }
-                } yield result
-                test.map { result =>
-                    val (writtenTickets, foundTickets) = result
-                    assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
-                    assertEquals(
-                        foundTickets
-                            .sortBy(_.number)
-                            .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
-                        expectedTickets.sortBy(_.number)
-                    )
-                }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("allTickets must respect given filters for status".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(generatedTickets)) =>
-                val defaultTimestamp = OffsetDateTime.now()
-                val tickets =
-                    generatedTickets
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, generatedTickets: NonEmptyList[Ticket]) =>
+            val defaultTimestamp = OffsetDateTime.now()
+            val tickets =
+                generatedTickets.toList
+                    .sortBy(_.number)
+                    .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
+            val statusFlags     = tickets.map(_.status).distinct.take(2)
+            val expectedTickets = tickets.filter(t => statusFlags.exists(_ === t.status))
+            val filter          = TicketFilter(Nil, status = statusFlags, Nil, Nil)
+            val submitters      = tickets.map(_.submitter).flatten
+            val project         = generatedProject.copy(owner = owner)
+            val dbConfig        = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((0, Nil))
+                    case Some(projectId) =>
+                        for {
+                            writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
+                        } yield (writtenTickets.sum, foundTickets)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (writtenTickets, foundTickets) = result
+                assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
+                assertEquals(
+                    foundTickets
                         .sortBy(_.number)
-                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
-                val statusFlags     = tickets.map(_.status).distinct.take(2)
-                val expectedTickets = tickets.filter(t => statusFlags.exists(_ === t.status))
-                val filter          = TicketFilter(Nil, status = statusFlags, Nil, Nil)
-                val submitters      = tickets.map(_.submitter).flatten
-                val project         = generatedProject.copy(owner = owner)
-                val dbConfig        = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
+                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
+                    expectedTickets.sortBy(_.number)
                 )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((0, Nil))
-                        case Some(projectId) =>
-                            for {
-                                writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
-                            } yield (writtenTickets.sum, foundTickets)
-                    }
-                } yield result
-                test.map { result =>
-                    val (writtenTickets, foundTickets) = result
-                    assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
-                    assertEquals(
-                        foundTickets
-                            .sortBy(_.number)
-                            .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
-                        expectedTickets.sortBy(_.number)
-                    )
-                }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("allTickets must respect given filters for resolution".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(generatedTickets)) =>
-                val defaultTimestamp = OffsetDateTime.now()
-                val tickets =
-                    generatedTickets
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, generatedTickets: NonEmptyList[Ticket]) =>
+            val defaultTimestamp = OffsetDateTime.now()
+            val tickets =
+                generatedTickets.toList
+                    .sortBy(_.number)
+                    .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
+            val resolutions     = tickets.map(_.resolution).flatten.distinct.take(2)
+            val expectedTickets = tickets.filter(t => t.resolution.exists(r => resolutions.exists(_ === r)))
+            val filter          = TicketFilter(Nil, Nil, resolution = resolutions, Nil)
+            val submitters      = tickets.map(_.submitter).flatten
+            val project         = generatedProject.copy(owner = owner)
+            val dbConfig        = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((0, Nil))
+                    case Some(projectId) =>
+                        for {
+                            writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
+                        } yield (writtenTickets.sum, foundTickets)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (writtenTickets, foundTickets) = result
+                assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
+                assertEquals(
+                    foundTickets
                         .sortBy(_.number)
-                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
-                val resolutions     = tickets.map(_.resolution).flatten.distinct.take(2)
-                val expectedTickets = tickets.filter(t => t.resolution.exists(r => resolutions.exists(_ === r)))
-                val filter          = TicketFilter(Nil, Nil, resolution = resolutions, Nil)
-                val submitters      = tickets.map(_.submitter).flatten
-                val project         = generatedProject.copy(owner = owner)
-                val dbConfig        = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
+                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
+                    expectedTickets.sortBy(_.number)
                 )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((0, Nil))
-                        case Some(projectId) =>
-                            for {
-                                writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
-                            } yield (writtenTickets.sum, foundTickets)
-                    }
-                } yield result
-                test.map { result =>
-                    val (writtenTickets, foundTickets) = result
-                    assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
-                    assertEquals(
-                        foundTickets
-                            .sortBy(_.number)
-                            .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
-                        expectedTickets.sortBy(_.number)
-                    )
-                }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("allTickets must respect given filters for submitter".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(generatedTickets)) =>
-                val defaultTimestamp = OffsetDateTime.now()
-                val tickets =
-                    generatedTickets
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, generatedTickets: NonEmptyList[Ticket]) =>
+            val defaultTimestamp = OffsetDateTime.now()
+            val tickets =
+                generatedTickets.toList
+                    .sortBy(_.number)
+                    .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
+            val submitters       = tickets.map(_.submitter).flatten
+            val wantedSubmitters = submitters.take(submitters.size / 2)
+            val expectedTickets  = tickets.filter(t => t.submitter.exists(s => wantedSubmitters.exists(_ === s)))
+            val filter           = TicketFilter(Nil, Nil, Nil, submitter = wantedSubmitters.map(_.name))
+            val project          = generatedProject.copy(owner = owner)
+            val dbConfig         = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((0, Nil))
+                    case Some(projectId) =>
+                        for {
+                            writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
+                        } yield (writtenTickets.sum, foundTickets)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (writtenTickets, foundTickets) = result
+                assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
+                assertEquals(
+                    foundTickets
                         .sortBy(_.number)
-                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp))
-                val submitters       = tickets.map(_.submitter).flatten
-                val wantedSubmitters = submitters.take(submitters.size / 2)
-                val expectedTickets  = tickets.filter(t => t.submitter.exists(s => wantedSubmitters.exists(_ === s)))
-                val filter           = TicketFilter(Nil, Nil, Nil, submitter = wantedSubmitters.map(_.name))
-                val project          = generatedProject.copy(owner = owner)
-                val dbConfig         = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
+                        .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
+                    expectedTickets.sortBy(_.number)
                 )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((0, Nil))
-                        case Some(projectId) =>
-                            for {
-                                writtenTickets <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                foundTickets   <- ticketRepo.allTickets(filter.some)(projectId).compile.toList
-                            } yield (writtenTickets.sum, foundTickets)
-                    }
-                } yield result
-                test.map { result =>
-                    val (writtenTickets, foundTickets) = result
-                    assertEquals(writtenTickets, tickets.size, "Wrong number of tickets created in the database!")
-                    assertEquals(
-                        foundTickets
-                            .sortBy(_.number)
-                            .map(_.copy(createdAt = defaultTimestamp, updatedAt = defaultTimestamp)),
-                        expectedTickets.sortBy(_.number)
-                    )
-                }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("createTicket must save the ticket to the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _           <- createProjectOwner(owner)
-                    _           <- ticket.submitter.traverse(createTicketsSubmitter)
-                    _           <- createTicketsProject(project)
-                    projectId   <- loadProjectId(owner.uid, project.name)
-                    _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
-                    foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
-                } yield foundTicket.getOrElse(None)
-                test.map { foundTicket =>
-                    foundTicket match {
-                        case None => fail("Created ticket not found!")
-                        case Some(foundTicket) =>
-                            assertEquals(
-                                foundTicket,
-                                ticket.copy(createdAt = foundTicket.createdAt, updatedAt = foundTicket.updatedAt)
-                            )
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _           <- createProjectOwner(owner)
+                _           <- ticket.submitter.traverse(createTicketsSubmitter)
+                _           <- createTicketsProject(project)
+                projectId   <- loadProjectId(owner.uid, project.name)
+                _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
+                foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
+            } yield foundTicket.getOrElse(None)
+            test.start.flatMap(_.joinWithNever).map { foundTicket =>
+                foundTicket match {
+                    case None => fail("Created ticket not found!")
+                    case Some(foundTicket) =>
+                        assertEquals(
+                            foundTicket,
+                            ticket.copy(createdAt = foundTicket.createdAt, updatedAt = foundTicket.updatedAt)
+                        )
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("deleteTicket must remove the ticket from the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _           <- createProjectOwner(owner)
-                    _           <- ticket.submitter.traverse(createTicketsSubmitter)
-                    _           <- createTicketsProject(project)
-                    projectId   <- loadProjectId(owner.uid, project.name)
-                    _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
-                    _           <- projectId.traverse(projectId => ticketRepo.deleteTicket(projectId)(ticket))
-                    foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
-                } yield foundTicket.getOrElse(None)
-                test.map { foundTicket =>
-                    assertEquals(foundTicket, None, "Ticket was not deleted from database!")
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _           <- createProjectOwner(owner)
+                _           <- ticket.submitter.traverse(createTicketsSubmitter)
+                _           <- createTicketsProject(project)
+                projectId   <- loadProjectId(owner.uid, project.name)
+                _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
+                _           <- projectId.traverse(projectId => ticketRepo.deleteTicket(projectId)(ticket))
+                foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
+            } yield foundTicket.getOrElse(None)
+            test.start.flatMap(_.joinWithNever).map { foundTicket =>
+                assertEquals(foundTicket, None, "Ticket was not deleted from database!")
+            }
         }
     }
 
     test("findTicket must find existing tickets".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(tickets)) =>
-                val expectedTicket = tickets(scala.util.Random.nextInt(tickets.size))
-                val submitters     = tickets.map(_.submitter).flatten
-                val project        = generatedProject.copy(owner = owner)
-                val dbConfig       = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    _ <- projectId match {
-                        case None            => IO.pure(Nil)
-                        case Some(projectId) => tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                    }
-                    foundTicket <- projectId.traverse(projectId =>
-                        ticketRepo.findTicket(projectId)(expectedTicket.number)
-                    )
-                } yield foundTicket.getOrElse(None)
-                test.map { foundTicket =>
-                    foundTicket match {
-                        case None => fail("Ticket not found!")
-                        case Some(foundTicket) =>
-                            assertEquals(
-                                foundTicket,
-                                expectedTicket.copy(
-                                    createdAt = foundTicket.createdAt,
-                                    updatedAt = foundTicket.updatedAt
-                                )
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, tickets: NonEmptyList[Ticket]) =>
+            val expectedTicket = tickets.toList(scala.util.Random.nextInt(tickets.size))
+            val submitters     = tickets.toList.map(_.submitter).flatten
+            val project        = generatedProject.copy(owner = owner)
+            val dbConfig       = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                _ <- projectId match {
+                    case None => IO.pure(Nil)
+                    case Some(projectId) =>
+                        tickets.toList.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                }
+                foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(expectedTicket.number))
+            } yield foundTicket.getOrElse(None)
+            test.start.flatMap(_.joinWithNever).map { foundTicket =>
+                foundTicket match {
+                    case None => fail("Ticket not found!")
+                    case Some(foundTicket) =>
+                        assertEquals(
+                            foundTicket,
+                            expectedTicket.copy(
+                                createdAt = foundTicket.createdAt,
+                                updatedAt = foundTicket.updatedAt
                             )
-                    }
+                        )
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("findTicketId must find the unique internal id of existing tickets".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTickets.sample) match {
-            case (Some(owner), Some(generatedProject), Some(tickets)) =>
-                val expectedTicket = tickets(scala.util.Random.nextInt(tickets.size))
-                val submitters     = tickets.map(_.submitter).flatten
-                val project        = generatedProject.copy(owner = owner)
-                val dbConfig       = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- submitters.traverse(createTicketsSubmitter)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => IO.pure((None, None))
-                        case Some(projectId) =>
-                            for {
-                                _ <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
-                                expectedTicketId <- loadTicketId(projectId, expectedTicket.number)
-                                foundTicketId    <- ticketRepo.findTicketId(projectId)(expectedTicket.number)
-                            } yield (expectedTicketId, foundTicketId)
-                    }
-                } yield result
-                test.map { result =>
-                    val (expectedTicketId, foundTicketId) = result
-                    assert(expectedTicketId.nonEmpty, "Expected ticket id not found!")
-                    assertEquals(foundTicketId, expectedTicketId)
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, tickets: NonEmptyList[Ticket]) =>
+            val expectedTicket = tickets.toList(scala.util.Random.nextInt(tickets.size))
+            val submitters     = tickets.toList.map(_.submitter).flatten
+            val project        = generatedProject.copy(owner = owner)
+            val dbConfig       = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- submitters.traverse(createTicketsSubmitter)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => IO.pure((None, None))
+                    case Some(projectId) =>
+                        for {
+                            _                <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket))
+                            expectedTicketId <- loadTicketId(projectId, expectedTicket.number)
+                            foundTicketId    <- ticketRepo.findTicketId(projectId)(expectedTicket.number)
+                        } yield (expectedTicketId, foundTicketId)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (expectedTicketId, foundTicketId) = result
+                assert(expectedTicketId.nonEmpty, "Expected ticket id not found!")
+                assertEquals(foundTicketId, expectedTicketId)
+            }
         }
     }
 
     test("loadAssignees must return all assignees of a ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genTicketsUsers.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(users)) =>
+        PropF.forAllF {
+            (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, users: NonEmptyList[TicketsUser]) =>
                 val assignees =
-                    users.map(user => Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString)))
+                    users.toList.map(user => Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString)))
                 val project  = generatedProject.copy(owner = owner)
                 val dbConfig = configuration.database
                 val tx = Transactor.fromDriverManager[IO](
@@ -681,24 +657,23 @@
                             } yield foundAssignees
                     }
                 } yield foundAssignees
-                test.map { foundAssignees =>
+                test.start.flatMap(_.joinWithNever).map { foundAssignees =>
                     assertEquals(foundAssignees.sortBy(_.name), assignees.sortBy(_.name))
                 }
-            case _ => fail("Could not generate data samples!")
         }
     }
 
     test("loadComments must return all comments of a ticket".tag(NeedsDatabase)) {
-        (
-            genProjectOwner.sample,
-            genProject.sample,
-            genTicket.sample,
-            genTicketsUser.sample,
-            genTicketComments.sample
-        ) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(user), Some(genComments)) =>
+        PropF.forAllF {
+            (
+                owner: ProjectOwner,
+                generatedProject: Project,
+                ticket: Ticket,
+                user: TicketsUser,
+                genComments: NonEmptyList[TicketComment]
+            ) =>
                 // Avoid time zone trouble.
-                val comments = genComments
+                val comments = genComments.toList
                     .modifyAll(_.each.createdAt, _.each.updatedAt)
                     .using(_.withOffsetSameInstant(ZoneOffset.UTC))
                 val project  = generatedProject.copy(owner = owner)
@@ -731,55 +706,52 @@
                             } yield foundComments
                     }
                 } yield foundComments
-                test.map { foundComments =>
+                test.start.flatMap(_.joinWithNever).map { foundComments =>
                     assertEquals(foundComments.sorted, comments.sorted)
                 }
-            case _ => fail("Could not generate data samples!")
         }
     }
 
     test("loadLabels must return all labels of a ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genLabels.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(labels)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val labelRepo  = new DoobieLabelRepository[IO](tx)
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    result <- projectId match {
-                        case None => fail("Project ID not found in database!")
-                        case Some(projectId) =>
-                            for {
-                                _             <- ticket.submitter.traverse(createTicketsSubmitter)
-                                _             <- ticketRepo.createTicket(projectId)(ticket)
-                                _             <- labels.traverse(label => labelRepo.createLabel(projectId)(label))
-                                createdLabels <- labelRepo.allLabels(projectId).compile.toList
-                                _ <- createdLabels.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
-                                foundLabels <- ticketRepo.loadLabels(projectId)(ticket.number).compile.toList
-                            } yield (createdLabels, foundLabels)
-                    }
-                } yield result
-                test.map { result =>
-                    val (createdLabels, foundLabels) = result
-                    assertEquals(foundLabels.sortBy(_.id), createdLabels.sortBy(_.id))
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, labels: NonEmptyList[Label]) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val labelRepo  = new DoobieLabelRepository[IO](tx)
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                result <- projectId match {
+                    case None => fail("Project ID not found in database!")
+                    case Some(projectId) =>
+                        for {
+                            _             <- ticket.submitter.traverse(createTicketsSubmitter)
+                            _             <- ticketRepo.createTicket(projectId)(ticket)
+                            _             <- labels.traverse(label => labelRepo.createLabel(projectId)(label))
+                            createdLabels <- labelRepo.allLabels(projectId).compile.toList
+                            _ <- createdLabels.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
+                            foundLabels <- ticketRepo.loadLabels(projectId)(ticket.number).compile.toList
+                        } yield (createdLabels, foundLabels)
+                }
+            } yield result
+            test.start.flatMap(_.joinWithNever).map { result =>
+                val (createdLabels, foundLabels) = result
+                assertEquals(foundLabels.sortBy(_.id), createdLabels.sortBy(_.id))
+            }
         }
     }
 
     test("loadMilestones must return all milestones of a ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genMilestones.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(milestones)) =>
+        PropF.forAllF {
+            (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, milestones: NonEmptyList[Milestone]) =>
                 val project  = generatedProject.copy(owner = owner)
                 val dbConfig = configuration.database
                 val tx = Transactor.fromDriverManager[IO](
@@ -812,173 +784,161 @@
                             } yield (createdMilestones, foundMilestones)
                     }
                 } yield result
-                test.map { result =>
+                test.start.flatMap(_.joinWithNever).map { result =>
                     val (createdMilestones, foundMilestones) = result
                     assertEquals(foundMilestones.sortBy(_.title), createdMilestones.sortBy(_.title))
                 }
-            case _ => fail("Could not generate data samples!")
         }
     }
 
     test("removeAssignee must remove the assignees from the ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genTicketsUser.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(user)) =>
-                val assignee = Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString))
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsUser(user)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    _         <- ticket.submitter.traverse(createTicketsSubmitter)
-                    foundAssignees <- projectId match {
-                        case None => IO.pure(Nil)
-                        case Some(projectId) =>
-                            for {
-                                _              <- ticketRepo.createTicket(projectId)(ticket)
-                                _              <- ticketRepo.addAssignee(projectId)(ticket.number)(assignee)
-                                _              <- ticketRepo.removeAssignee(projectId)(ticket)(assignee)
-                                foundAssignees <- ticketRepo.loadAssignees(projectId)(ticket.number).compile.toList
-                            } yield foundAssignees
-                    }
-                } yield foundAssignees
-                test.map { foundAssignees =>
-                    assertEquals(foundAssignees, Nil)
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, user: TicketsUser) =>
+            val assignee = Assignee(AssigneeId(user.uid.toUUID), AssigneeName(user.name.toString))
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsUser(user)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                _         <- ticket.submitter.traverse(createTicketsSubmitter)
+                foundAssignees <- projectId match {
+                    case None => IO.pure(Nil)
+                    case Some(projectId) =>
+                        for {
+                            _              <- ticketRepo.createTicket(projectId)(ticket)
+                            _              <- ticketRepo.addAssignee(projectId)(ticket.number)(assignee)
+                            _              <- ticketRepo.removeAssignee(projectId)(ticket)(assignee)
+                            foundAssignees <- ticketRepo.loadAssignees(projectId)(ticket.number).compile.toList
+                        } yield foundAssignees
+                }
+            } yield foundAssignees
+            test.start.flatMap(_.joinWithNever).map { foundAssignees =>
+                assertEquals(foundAssignees, Nil)
+            }
         }
     }
 
     test("removeLabel must remove the label from the ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(label)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val labelRepo  = new DoobieLabelRepository[IO](tx)
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    foundLabels <- projectId match {
-                        case None => fail("Project ID not found in database!")
-                        case Some(projectId) =>
-                            for {
-                                _            <- ticket.submitter.traverse(createTicketsSubmitter)
-                                _            <- labelRepo.createLabel(projectId)(label)
-                                createdLabel <- labelRepo.findLabel(projectId)(label.name)
-                                _            <- ticketRepo.createTicket(projectId)(ticket)
-                                _ <- createdLabel.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
-                                _ <- createdLabel.traverse(cl => ticketRepo.removeLabel(projectId)(ticket)(cl))
-                                foundLabels <- loadTicketLabelIds(projectId, ticket.number)
-                            } yield foundLabels
-                    }
-                } yield foundLabels
-                test.map { foundLabels =>
-                    assertEquals(foundLabels, Nil)
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, label: Label) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val labelRepo  = new DoobieLabelRepository[IO](tx)
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                foundLabels <- projectId match {
+                    case None => fail("Project ID not found in database!")
+                    case Some(projectId) =>
+                        for {
+                            _            <- ticket.submitter.traverse(createTicketsSubmitter)
+                            _            <- labelRepo.createLabel(projectId)(label)
+                            createdLabel <- labelRepo.findLabel(projectId)(label.name)
+                            _            <- ticketRepo.createTicket(projectId)(ticket)
+                            _ <- createdLabel.traverse(cl => ticketRepo.addLabel(projectId)(ticket.number)(cl))
+                            _ <- createdLabel.traverse(cl => ticketRepo.removeLabel(projectId)(ticket)(cl))
+                            foundLabels <- loadTicketLabelIds(projectId, ticket.number)
+                        } yield foundLabels
+                }
+            } yield foundLabels
+            test.start.flatMap(_.joinWithNever).map { foundLabels =>
+                assertEquals(foundLabels, Nil)
+            }
         }
     }
 
     test("removeMilestone must remove the milestone from the ticket".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genMilestone.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(milestone)) =>
-                val project  = generatedProject.copy(owner = owner)
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val milestoneRepo = new DoobieMilestoneRepository[IO](tx)
-                val ticketRepo    = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    foundMilestones <- projectId match {
-                        case None => fail("Project ID not found in database!")
-                        case Some(projectId) =>
-                            for {
-                                _                <- ticket.submitter.traverse(createTicketsSubmitter)
-                                _                <- milestoneRepo.createMilestone(projectId)(milestone)
-                                createdMilestone <- milestoneRepo.findMilestone(projectId)(milestone.title)
-                                _                <- ticketRepo.createTicket(projectId)(ticket)
-                                _ <- createdMilestone.traverse(ms =>
-                                    ticketRepo.addMilestone(projectId)(ticket.number)(ms)
-                                )
-                                _ <- createdMilestone.traverse(ms => ticketRepo.removeMilestone(projectId)(ticket)(ms))
-                                foundMilestones <- loadTicketMilestoneIds(projectId, ticket.number)
-                            } yield foundMilestones
-                    }
-                } yield foundMilestones
-                test.map { foundMilestones =>
-                    assertEquals(foundMilestones, Nil)
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, milestone: Milestone) =>
+            val project  = generatedProject.copy(owner = owner)
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val milestoneRepo = new DoobieMilestoneRepository[IO](tx)
+            val ticketRepo    = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                foundMilestones <- projectId match {
+                    case None => fail("Project ID not found in database!")
+                    case Some(projectId) =>
+                        for {
+                            _                <- ticket.submitter.traverse(createTicketsSubmitter)
+                            _                <- milestoneRepo.createMilestone(projectId)(milestone)
+                            createdMilestone <- milestoneRepo.findMilestone(projectId)(milestone.title)
+                            _                <- ticketRepo.createTicket(projectId)(ticket)
+                            _ <- createdMilestone.traverse(ms => ticketRepo.addMilestone(projectId)(ticket.number)(ms))
+                            _ <- createdMilestone.traverse(ms => ticketRepo.removeMilestone(projectId)(ticket)(ms))
+                            foundMilestones <- loadTicketMilestoneIds(projectId, ticket.number)
+                        } yield foundMilestones
+                }
+            } yield foundMilestones
+            test.start.flatMap(_.joinWithNever).map { foundMilestones =>
+                assertEquals(foundMilestones, Nil)
+            }
         }
     }
 
     test("updateTicket must update the ticket in the database".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genTicket.sample, genTicket.sample) match {
-            case (Some(owner), Some(generatedProject), Some(ticket), Some(anotherTicket)) =>
-                val project = generatedProject.copy(owner = owner)
-                val updatedTicket =
-                    ticket.copy(
-                        title = anotherTicket.title,
-                        content = anotherTicket.content,
-                        submitter = anotherTicket.submitter
-                    )
-                val dbConfig = configuration.database
-                val tx = Transactor.fromDriverManager[IO](
-                    driver = dbConfig.driver,
-                    url = dbConfig.url,
-                    user = dbConfig.user,
-                    password = dbConfig.pass,
-                    logHandler = None
-                )
-                val ticketRepo = new DoobieTicketRepository[IO](tx)
-                val test = for {
-                    _           <- createProjectOwner(owner)
-                    _           <- ticket.submitter.traverse(createTicketsSubmitter)
-                    _           <- updatedTicket.submitter.traverse(createTicketsSubmitter)
-                    _           <- createTicketsProject(project)
-                    projectId   <- loadProjectId(owner.uid, project.name)
-                    _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
-                    _           <- projectId.traverse(projectId => ticketRepo.updateTicket(projectId)(updatedTicket))
-                    foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
-                } yield foundTicket.getOrElse(None)
-                test.map { foundTicket =>
-                    foundTicket match {
-                        case None => fail("Created ticket not found!")
-                        case Some(foundTicket) =>
-                            assertEquals(
-                                foundTicket,
-                                updatedTicket.copy(createdAt = foundTicket.createdAt, updatedAt = foundTicket.updatedAt)
-                            )
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, ticket: Ticket, anotherTicket: Ticket) =>
+            val project = generatedProject.copy(owner = owner)
+            val updatedTicket =
+                ticket.copy(
+                    title = anotherTicket.title,
+                    content = anotherTicket.content,
+                    submitter = anotherTicket.submitter
+                )
+            val dbConfig = configuration.database
+            val tx = Transactor.fromDriverManager[IO](
+                driver = dbConfig.driver,
+                url = dbConfig.url,
+                user = dbConfig.user,
+                password = dbConfig.pass,
+                logHandler = None
+            )
+            val ticketRepo = new DoobieTicketRepository[IO](tx)
+            val test = for {
+                _           <- createProjectOwner(owner)
+                _           <- ticket.submitter.traverse(createTicketsSubmitter)
+                _           <- updatedTicket.submitter.traverse(createTicketsSubmitter)
+                _           <- createTicketsProject(project)
+                projectId   <- loadProjectId(owner.uid, project.name)
+                _           <- projectId.traverse(projectId => ticketRepo.createTicket(projectId)(ticket))
+                _           <- projectId.traverse(projectId => ticketRepo.updateTicket(projectId)(updatedTicket))
+                foundTicket <- projectId.traverse(projectId => ticketRepo.findTicket(projectId)(ticket.number))
+            } yield foundTicket.getOrElse(None)
+            test.start.flatMap(_.joinWithNever).map { foundTicket =>
+                foundTicket match {
+                    case None => fail("Created ticket not found!")
+                    case Some(foundTicket) =>
+                        assertEquals(
+                            foundTicket,
+                            updatedTicket.copy(createdAt = foundTicket.createdAt, updatedAt = foundTicket.updatedAt)
+                        )
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
-
 }
diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/tickets/Generators.scala new-smederee/modules/hub/src/test/scala/de/smederee/tickets/Generators.scala
--- old-smederee/modules/hub/src/test/scala/de/smederee/tickets/Generators.scala	2025-01-10 23:53:14.300502869 +0000
+++ new-smederee/modules/hub/src/test/scala/de/smederee/tickets/Generators.scala	2025-01-10 23:53:14.300502869 +0000
@@ -119,6 +119,8 @@
         language <- Gen.option(genLanguageCode)
     } yield TicketsUser(uid, name, email, language)
 
+    given Arbitrary[TicketsUser] = Arbitrary(genTicketsUser)
+
     val genTicketsUsers: Gen[List[TicketsUser]] = Gen.nonEmptyListOf(genTicketsUser)
 
     val genTicket: Gen[Ticket] = for {
@@ -155,6 +157,8 @@
             updatedAt <- genOffsetDateTime
         } yield TicketComment(content, submitter, createdAt, updatedAt)
 
+    given Arbitrary[TicketComment] = Arbitrary(genTicketComment)
+
     val genTicketComments: Gen[List[TicketComment]] = Gen.nonEmptyListOf(genTicketComment)
 
     val genTicketFilter: Gen[TicketFilter] =