~jan0sch/smederee

Showing details for patch 3b17a69c78562b980024d43506690f740d948287.
2024-07-28 (Sun), 12:58 PM - Jens Grassel - 3b17a69c78562b980024d43506690f740d948287

chore: migrate test to ScalaCheckEffect

Summary of changes
3 files modified with 210 lines added and 214 lines removed
  • modules/hub/src/test/scala/de/smederee/tickets/BaseSpec.scala with 1 added and 1 removed lines
  • modules/hub/src/test/scala/de/smederee/tickets/DoobieLabelRepositoryTest.scala with 205 added and 213 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/BaseSpec.scala new-smederee/modules/hub/src/test/scala/de/smederee/tickets/BaseSpec.scala
--- old-smederee/modules/hub/src/test/scala/de/smederee/tickets/BaseSpec.scala	2025-01-10 23:57:48.741013515 +0000
+++ new-smederee/modules/hub/src/test/scala/de/smederee/tickets/BaseSpec.scala	2025-01-10 23:57:48.741013515 +0000
@@ -31,7 +31,7 @@
   * does initialise the test database for each suite. The latter means a possibly existing database with the name
   * configured **will be deleted**!
   */
-abstract class BaseSpec extends CatsEffectSuite with ScalaCheckCats {
+abstract class BaseSpec extends CatsEffectSuite with ScalaCheckEffectSuite with ScalaCheckCats {
 
     protected final val configuration: SmedereeTicketsConfiguration =
         ConfigSource
diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieLabelRepositoryTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieLabelRepositoryTest.scala
--- old-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieLabelRepositoryTest.scala	2025-01-10 23:57:48.741013515 +0000
+++ new-smederee/modules/hub/src/test/scala/de/smederee/tickets/DoobieLabelRepositoryTest.scala	2025-01-10 23:57:48.741013515 +0000
@@ -6,13 +6,17 @@
 
 package de.smederee.tickets
 
+import cats.data.NonEmptyList
 import cats.effect.*
 import cats.syntax.all.*
 import de.smederee.TestTags.*
-import de.smederee.tickets.Generators.*
+import de.smederee.tickets.Generators.given
 import doobie.*
 
+import org.scalacheck.effect.PropF
+
 final class DoobieLabelRepositoryTest extends BaseSpec {
+    override def scalaCheckTestParameters = super.scalaCheckTestParameters.withMinSuccessfulTests(1)
 
     /** Find the label ID for the given project and label name.
       *
@@ -56,246 +60,234 @@
         }
 
     test("allLabels must return all labels".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabels.sample) match {
-            case (Some(owner), Some(generatedProject), 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 test = for {
-                    _         <- createProjectOwner(owner)
-                    _         <- createTicketsProject(project)
-                    projectId <- loadProjectId(owner.uid, project.name)
-                    createdLabels <- projectId match {
-                        case None            => IO.pure(List.empty)
-                        case Some(projectId) => labels.traverse(label => labelRepo.createLabel(projectId)(label))
-                    }
-                    foundLabels <- projectId match {
-                        case None            => IO.pure(List.empty)
-                        case Some(projectId) => labelRepo.allLabels(projectId).compile.toList
-                    }
-                } yield foundLabels
-                test.map { foundLabels =>
-                    assert(foundLabels.size === labels.size, "Different number of labels!")
-                    foundLabels.sortBy(_.name).zip(labels.sortBy(_.name)).map { (found, expected) =>
-                        assertEquals(found.copy(id = expected.id), expected)
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, 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 test = for {
+                _         <- createProjectOwner(owner)
+                _         <- createTicketsProject(project)
+                projectId <- loadProjectId(owner.uid, project.name)
+                createdLabels <- projectId match {
+                    case None            => IO.pure(List.empty)
+                    case Some(projectId) => labels.toList.traverse(label => labelRepo.createLabel(projectId)(label))
                 }
-            case _ => fail("Could not generate data samples!")
+                foundLabels <- projectId match {
+                    case None            => IO.pure(List.empty)
+                    case Some(projectId) => labelRepo.allLabels(projectId).compile.toList
+                }
+            } yield foundLabels
+            test.start.flatMap(_.joinWithNever).map { foundLabels =>
+                assert(foundLabels.size === labels.size, "Different number of labels!")
+                val cleanedFound =
+                    foundLabels.sortBy(_.name).zip(labels.toList.sortBy(_.name)).map { (found, expected) =>
+                        found.copy(id = expected.id)
+                    }
+                assertEquals(cleanedFound.sortBy(_.name), labels.toList.sortBy(_.name))
+            }
         }
     }
 
     test("createLabel must create the label".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), 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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                    foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(label.name))
-                } yield (createdProjects, projectId, createdLabels, foundLabel)
-                test.map { tuple =>
-                    val (createdProjects, projectId, createdLabels, foundLabel) = tuple
-                    assert(createdProjects === 1, "Test project was not created!")
-                    assert(projectId.nonEmpty, "No project id found!")
-                    assert(createdLabels.exists(_ === 1), "Test label was not created!")
-                    foundLabel.getOrElse(None) match {
-                        case None => fail("Created label not found!")
-                        case Some(foundLabel) =>
-                            assert(foundLabel.id.nonEmpty, "Label ID must not be empty!")
-                            assertEquals(foundLabel.name, label.name)
-                            assertEquals(foundLabel.description, label.description)
-                            assertEquals(foundLabel.colour, label.colour)
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, 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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+                foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(label.name))
+            } yield (createdProjects, projectId, createdLabels, foundLabel)
+            test.start.flatMap(_.joinWithNever).map { tuple =>
+                val (createdProjects, projectId, createdLabels, foundLabel) = tuple
+                assert(createdProjects === 1, "Test project was not created!")
+                assert(projectId.nonEmpty, "No project id found!")
+                assert(createdLabels.exists(_ === 1), "Test label was not created!")
+                foundLabel.getOrElse(None) match {
+                    case None => fail("Created label not found!")
+                    case Some(foundLabel) =>
+                        assert(foundLabel.id.nonEmpty, "Label ID must not be empty!")
+                        assertEquals(foundLabel.name, label.name)
+                        assertEquals(foundLabel.description, label.description)
+                        assertEquals(foundLabel.colour, label.colour)
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("createLabel must fail if the label name already exists".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), 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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                    _               <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                } yield (createdProjects, projectId, createdLabels)
-                test.attempt.map(r => assert(r.isLeft, "Creating a label with a duplicate name must fail!"))
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, 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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+                _               <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+            } yield (createdProjects, projectId, createdLabels)
+            test.attempt.map(r => assert(r.isLeft, "Creating a label with a duplicate name must fail!"))
         }
     }
 
     test("deleteLabel must delete an existing label".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), 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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                    labelId         <- findLabelId(owner.uid, project.name, label.name)
-                    deletedLabels   <- labelRepo.deleteLabel(label.copy(id = labelId.flatMap(LabelId.from)))
-                    foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(label.name))
-                } yield (createdProjects, projectId, createdLabels, deletedLabels, foundLabel)
-                test.map { tuple =>
-                    val (createdProjects, projectId, createdLabels, deletedLabels, foundLabel) = tuple
-                    assert(createdProjects === 1, "Test vcs project was not created!")
-                    assert(projectId.nonEmpty, "No vcs project id found!")
-                    assert(createdLabels.exists(_ === 1), "Test label was not created!")
-                    assert(deletedLabels === 1, "Test label was not deleted!")
-                    assert(foundLabel.getOrElse(None).isEmpty, "Test label was not deleted!")
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, 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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+                labelId         <- findLabelId(owner.uid, project.name, label.name)
+                deletedLabels   <- labelRepo.deleteLabel(label.copy(id = labelId.flatMap(LabelId.from)))
+                foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(label.name))
+            } yield (createdProjects, projectId, createdLabels, deletedLabels, foundLabel)
+            test.start.flatMap(_.joinWithNever).map { tuple =>
+                val (createdProjects, projectId, createdLabels, deletedLabels, foundLabel) = tuple
+                assert(createdProjects === 1, "Test vcs project was not created!")
+                assert(projectId.nonEmpty, "No vcs project id found!")
+                assert(createdLabels.exists(_ === 1), "Test label was not created!")
+                assert(deletedLabels === 1, "Test label was not deleted!")
+                assert(foundLabel.getOrElse(None).isEmpty, "Test label was not deleted!")
+            }
         }
     }
 
     test("findLabel must find existing labels".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabels.sample) match {
-            case (Some(owner), Some(generatedProject), Some(labels)) =>
-                val project       = generatedProject.copy(owner = owner)
-                val dbConfig      = configuration.database
-                val expectedLabel = labels(scala.util.Random.nextInt(labels.size))
-                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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels <- projectId match {
-                        case None            => IO.pure(List.empty)
-                        case Some(projectId) => labels.traverse(label => labelRepo.createLabel(projectId)(label))
-                    }
-                    foundLabel <- projectId.traverse(id => labelRepo.findLabel(id)(expectedLabel.name))
-                } yield foundLabel.flatten
-                test.map { foundLabel =>
-                    assertEquals(foundLabel.map(_.copy(id = expectedLabel.id)), Option(expectedLabel))
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, labels: NonEmptyList[Label]) =>
+            val project       = generatedProject.copy(owner = owner)
+            val dbConfig      = configuration.database
+            val expectedLabel = labels.toList(scala.util.Random.nextInt(labels.size))
+            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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels <- projectId match {
+                    case None            => IO.pure(List.empty)
+                    case Some(projectId) => labels.toList.traverse(label => labelRepo.createLabel(projectId)(label))
                 }
-            case _ => fail("Could not generate data samples!")
+                foundLabel <- projectId.traverse(id => labelRepo.findLabel(id)(expectedLabel.name))
+            } yield foundLabel.flatten
+            test.start.flatMap(_.joinWithNever).map { foundLabel =>
+                assertEquals(foundLabel.map(_.copy(id = expectedLabel.id)), Option(expectedLabel))
+            }
         }
     }
 
     test("updateLabel must update an existing label".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), Some(label)) =>
-                val updatedLabel = label.copy(
-                    name = LabelName("updated label"),
-                    description = Option(LabelDescription("I am an updated label description...")),
-                    colour = ColourCode("#abcdef")
-                )
-                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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                    labelId         <- findLabelId(owner.uid, project.name, label.name)
-                    updatedLabels   <- labelRepo.updateLabel(updatedLabel.copy(id = labelId.map(LabelId.apply)))
-                    foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(updatedLabel.name))
-                } yield (createdProjects, projectId, createdLabels, updatedLabels, foundLabel.flatten)
-                test.map { tuple =>
-                    val (createdProjects, projectId, createdLabels, updatedLabels, foundLabel) = tuple
-                    assert(createdProjects === 1, "Test vcs project was not created!")
-                    assert(projectId.nonEmpty, "No vcs project id found!")
-                    assert(createdLabels.exists(_ === 1), "Test label was not created!")
-                    assert(updatedLabels === 1, "Test label was not updated!")
-                    assert(foundLabel.nonEmpty, "Updated label not found!")
-                    foundLabel.map { label =>
-                        assertEquals(label, updatedLabel.copy(id = label.id))
-                    }
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, label: Label) =>
+            val updatedLabel = label.copy(
+                name = LabelName("updated label"),
+                description = Option(LabelDescription("I am an updated label description...")),
+                colour = ColourCode("#abcdef")
+            )
+            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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+                labelId         <- findLabelId(owner.uid, project.name, label.name)
+                updatedLabels   <- labelRepo.updateLabel(updatedLabel.copy(id = labelId.map(LabelId.apply)))
+                foundLabel      <- projectId.traverse(id => labelRepo.findLabel(id)(updatedLabel.name))
+            } yield (createdProjects, projectId, createdLabels, updatedLabels, foundLabel.flatten)
+            test.start.flatMap(_.joinWithNever).map { tuple =>
+                val (createdProjects, projectId, createdLabels, updatedLabels, foundLabel) = tuple
+                assert(createdProjects === 1, "Test vcs project was not created!")
+                assert(projectId.nonEmpty, "No vcs project id found!")
+                assert(createdLabels.exists(_ === 1), "Test label was not created!")
+                assert(updatedLabels === 1, "Test label was not updated!")
+                assert(foundLabel.nonEmpty, "Updated label not found!")
+                foundLabel.foreach { label =>
+                    assertEquals(label, updatedLabel.copy(id = label.id))
                 }
-            case _ => fail("Could not generate data samples!")
+            }
         }
     }
 
     test("updateLabel must do nothing if id attribute is empty".tag(NeedsDatabase)) {
-        (genProjectOwner.sample, genProject.sample, genLabel.sample) match {
-            case (Some(owner), Some(generatedProject), Some(label)) =>
-                val updatedLabel = label.copy(
-                    id = None,
-                    name = LabelName("updated label"),
-                    description = Option(LabelDescription("I am an updated label description...")),
-                    colour = ColourCode("#abcdef")
-                )
-                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 test = for {
-                    _               <- createProjectOwner(owner)
-                    createdProjects <- createTicketsProject(project)
-                    projectId       <- loadProjectId(owner.uid, project.name)
-                    createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
-                    labelId         <- findLabelId(owner.uid, project.name, label.name)
-                    updatedLabels   <- labelRepo.updateLabel(updatedLabel)
-                } yield (createdProjects, projectId, createdLabels, updatedLabels)
-                test.map { tuple =>
-                    val (createdProjects, projectId, createdLabels, updatedLabels) = tuple
-                    assert(createdProjects === 1, "Test vcs project was not created!")
-                    assert(projectId.nonEmpty, "No vcs project id found!")
-                    assert(createdLabels.exists(_ === 1), "Test label was not created!")
-                    assert(updatedLabels === 0, "Label with empty id must not be updated!")
-                }
-            case _ => fail("Could not generate data samples!")
+        PropF.forAllF { (owner: ProjectOwner, generatedProject: Project, label: Label) =>
+            val updatedLabel = label.copy(
+                id = None,
+                name = LabelName("updated label"),
+                description = Option(LabelDescription("I am an updated label description...")),
+                colour = ColourCode("#abcdef")
+            )
+            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 test = for {
+                _               <- createProjectOwner(owner)
+                createdProjects <- createTicketsProject(project)
+                projectId       <- loadProjectId(owner.uid, project.name)
+                createdLabels   <- projectId.traverse(id => labelRepo.createLabel(id)(label))
+                labelId         <- findLabelId(owner.uid, project.name, label.name)
+                updatedLabels   <- labelRepo.updateLabel(updatedLabel)
+            } yield (createdProjects, projectId, createdLabels, updatedLabels)
+            test.start.flatMap(_.joinWithNever).map { tuple =>
+                val (createdProjects, projectId, createdLabels, updatedLabels) = tuple
+                assert(createdProjects === 1, "Test vcs project was not created!")
+                assert(projectId.nonEmpty, "No vcs project id found!")
+                assert(createdLabels.exists(_ === 1), "Test label was not created!")
+                assert(updatedLabels === 0, "Label with empty id must not be updated!")
+            }
         }
     }
 }
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:57:48.741013515 +0000
+++ new-smederee/modules/hub/src/test/scala/de/smederee/tickets/Generators.scala	2025-01-10 23:57:48.741013515 +0000
@@ -210,6 +210,8 @@
         colour      <- genColourCode
     } yield Label(id, name, description, colour)
 
+    given Arbitrary[Label] = Arbitrary(genLabel)
+
     val genLabels: Gen[List[Label]] = Gen.nonEmptyListOf(genLabel).map(_.distinct)
 
     val genMilestoneTitle: Gen[MilestoneTitle] =
@@ -287,6 +289,8 @@
             isPrivate   <- Gen.oneOf(List(false, true))
         } yield Project(owner, name, description, isPrivate)
 
+    given Arbitrary[Project] = Arbitrary(genProject)
+
     val genProjects: Gen[List[Project]] = Gen.nonEmptyListOf(genProject)
 
 }