~jan0sch/smederee
Showing details for patch 1773d70487ed28ad395f7f0ec5f93ae4ce02421e.
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] =