From c5f47c3e33fd8af977807a6c511e18caf375e4fa Mon Sep 17 00:00:00 2001 From: "mike.toggweiler" Date: Thu, 19 Oct 2023 23:16:30 +0200 Subject: [PATCH] Upgraded libraries: * Play framework 2.8.20 * Latest 2.6.x akka version * replaced implementation of no longer maintained ai.x:play-json-extensions with custom enumFormat * flapdoodle.embed.mongo version bump to 4.9.2, adjust implementation to startup container in test --- .../scheduler/gitlab/GitlabModels.scala | 22 ++-- .../controllers/ApplicationController.scala | 2 +- .../views/UserTimeBookingStatisticsView.scala | 5 +- backend/app/models/BaseFormat.scala | 41 +++--- backend/app/models/Granularity.scala | 10 +- backend/app/models/Invitation.scala | 4 +- .../app/models/InvitationOutcomeStatus.scala | 7 +- .../app/models/InvitationStatusResponse.scala | 9 +- backend/app/models/OrganisationRole.scala | 8 +- backend/app/models/Tags.scala | 7 +- backend/app/models/UserProject.scala | 8 +- backend/app/models/UserRole.scala | 6 +- .../adapters/PersistedEventAdapter.scala | 4 +- backend/build.sbt | 33 ++--- backend/project/build.properties | 2 +- backend/project/plugins.sbt | 18 +-- .../test/core/MockSystemServicesAware.scala | 28 ++-- backend/test/models/JsonEnumSpec.scala | 121 ++++++++++++++++++ backend/test/mongo/MongoSetup.scala | 75 +++++------ 19 files changed, 247 insertions(+), 163 deletions(-) create mode 100644 backend/test/models/JsonEnumSpec.scala diff --git a/backend/app/actors/scheduler/gitlab/GitlabModels.scala b/backend/app/actors/scheduler/gitlab/GitlabModels.scala index 5fe7afb..91c25b7 100644 --- a/backend/app/actors/scheduler/gitlab/GitlabModels.scala +++ b/backend/app/actors/scheduler/gitlab/GitlabModels.scala @@ -21,12 +21,10 @@ package actors.scheduler.gitlab -import ai.x.play.json.Encoders.encoder -import ai.x.play.json.Jsonx import org.joda.time.DateTime import play.api.libs.json._ + import java.util.Date -import models.BaseFormat._ case class GitlabMilestone( due_date: Option[Date], @@ -85,23 +83,24 @@ case class GitlabIssue( assignees: Seq[GitlabUser], assignee: Option[GitlabUser], labels: Seq[String], - upvotes: Int, - downvotes: Int, - merge_requests_count: Int, + // remove to not exceed limit of 22 fields + // upvotes: Int, + // downvotes: Int, + // merge_requests_count: Int, id: Int, title: String, created_at: DateTime, updated_at: DateTime, closed_at: Option[DateTime], closed_by: Option[GitlabUser], - subscribed: Option[Boolean], - user_notes_count: Int, + // subscribed: Option[Boolean], + // user_notes_count: Int, due_date: Option[Date], web_url: String, references: Option[GitlabReference], time_stats: Option[GitlabTimeslot], confidential: Option[Boolean], - discussion_locked: Option[Boolean], + // discussion_locked: Option[Boolean], _links: Option[GitlabLinks], task_completion_status: Option[GitlabTaskCompletionStatus] ) @@ -117,6 +116,7 @@ case class GitlabIssuesSearchResult( ) object GitlabMilestone { + import models.BaseFormat._ implicit val jsonFormat: Format[GitlabMilestone] = Json.format[GitlabMilestone] } @@ -138,8 +138,8 @@ object GitlabTaskCompletionStatus { Json.format[GitlabTaskCompletionStatus] } object GitlabIssue { - implicit val jsonFormat: Format[GitlabIssue] = - Jsonx.formatCaseClass[GitlabIssue] + import models.BaseFormat._ + implicit val jsonFormat: Format[GitlabIssue] = Json.format[GitlabIssue] } object GitlabIssuesSearchResult { implicit val jsonFormat: Format[GitlabIssuesSearchResult] = diff --git a/backend/app/controllers/ApplicationController.scala b/backend/app/controllers/ApplicationController.scala index cd7c9ca..0a8f7d8 100644 --- a/backend/app/controllers/ApplicationController.scala +++ b/backend/app/controllers/ApplicationController.scala @@ -44,7 +44,7 @@ import scala.concurrent.{ExecutionContext, Future} case class LoginForm(email: String, password: String) object LoginForm { - implicit val loginFormFormat = Json.format[LoginForm] + implicit val loginFormFormat: OFormat[LoginForm] = Json.format[LoginForm] } class ApplicationController @Inject() ( diff --git a/backend/app/domain/views/UserTimeBookingStatisticsView.scala b/backend/app/domain/views/UserTimeBookingStatisticsView.scala index 08f784d..1b855af 100644 --- a/backend/app/domain/views/UserTimeBookingStatisticsView.scala +++ b/backend/app/domain/views/UserTimeBookingStatisticsView.scala @@ -40,7 +40,7 @@ import play.modules.reactivemongo.ReactiveMongoApi import repositories._ import scala.annotation.tailrec -import scala.concurrent.Await +import scala.concurrent.{Await, ExecutionContextExecutor} import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.{Failure, Success} @@ -81,7 +81,8 @@ class UserTimeBookingStatisticsView( private val waitTime = 5 seconds - private implicit val executionContext = context.dispatcher + private implicit val executionContext: ExecutionContextExecutor = + context.dispatcher override def restoreViewFromState(snapshot: UserTimeBooking): Unit = { println( diff --git a/backend/app/models/BaseFormat.scala b/backend/app/models/BaseFormat.scala index 3dd6a93..d3dcced 100644 --- a/backend/app/models/BaseFormat.scala +++ b/backend/app/models/BaseFormat.scala @@ -22,17 +22,14 @@ package models import com.tegonal.play.json.TypedId._ +import julienrf.json.derived +import julienrf.json.derived.{DerivedReads, TypeTag} import models.BaseFormat.CompositeBaseId -import org.joda.time.format.{DateTimeFormat, ISODateTimeFormat} -import org.joda.time.{ - DateTime, - DateTimeZone, - Duration, - LocalDate, - LocalDateTime -} +import org.joda.time.format.DateTimeFormat +import org.joda.time._ import play.api.libs.json._ import reactivemongo.api.bson._ +import shapeless.Lazy import java.net.{URI, URL} import java.util.UUID @@ -125,6 +122,20 @@ object BaseFormat { val defaultTypeFormat: OFormat[String] = (__ \ "type").format[String] + private val selfTypeFormat: OFormat[String] = __.format[String] + + private def toStringWrites[T]: Writes[T] = + Writes[T](obj => JsString(obj.toString)) + + def enumFormat[T](implicit + derivedReads: Lazy[DerivedReads[T, TypeTag.ShortClassName]] + ): Format[T] = { + implicit val reads: Reads[T] = derived.flat.reads(selfTypeFormat) + implicit val writes: Writes[T] = toStringWrites[T] + + Format[T](reads, writes) + } + val dateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ" implicit val dateFormat: Format[DateTime] = Format[DateTime]( JodaReads.jodaDateReads(dateTimePattern), @@ -133,9 +144,9 @@ object BaseFormat { Format.optionWithNull val localDateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.SSS" - val localDateTimeReads: Reads[LocalDateTime] = { + private val localDateTimeReads: Reads[LocalDateTime] = { new Reads[LocalDateTime] { - val df = + private val df = DateTimeFormat.forPattern(localDateTimePattern) def reads(json: JsValue): JsResult[LocalDateTime] = json match { @@ -160,7 +171,7 @@ object BaseFormat { .opt(LocalDateTime.parse(input, df)) } } - val localDateTimeWrites: Writes[LocalDateTime] = { + private val localDateTimeWrites: Writes[LocalDateTime] = { val df = org.joda.time.format.DateTimeFormat.forPattern(localDateTimePattern) Writes[LocalDateTime] { d => @@ -171,9 +182,9 @@ object BaseFormat { Format[LocalDateTime](localDateTimeReads, localDateTimeWrites) val localDatePattern = "yyyy-MM-dd" - val localDateReads: Reads[LocalDate] = { + private val localDateReads: Reads[LocalDate] = { new Reads[LocalDate] { - val df = + private val df = DateTimeFormat.forPattern(localDatePattern) def reads(json: JsValue): JsResult[LocalDate] = json match { @@ -198,7 +209,7 @@ object BaseFormat { .opt(LocalDate.parse(input, df)) } } - val localDateWrites: Writes[LocalDate] = { + private val localDateWrites: Writes[LocalDate] = { val df = org.joda.time.format.DateTimeFormat.forPattern(localDatePattern) Writes[LocalDate] { d => @@ -210,7 +221,7 @@ object BaseFormat { implicit object DateTimeZoneFormat extends Format[DateTimeZone] { def writes(zone: DateTimeZone): JsValue = { - JsString(zone.getID()) + JsString(zone.getID) } def reads(json: JsValue): JsResult[DateTimeZone] = json match { diff --git a/backend/app/models/Granularity.scala b/backend/app/models/Granularity.scala index 207a053..960b636 100644 --- a/backend/app/models/Granularity.scala +++ b/backend/app/models/Granularity.scala @@ -21,17 +21,13 @@ package models -import ai.x.play.json.Jsonx -import play.api.libs.json.Format +import models.BaseFormat.enumFormat +import play.api.libs.json._ sealed trait Granularity {} object Granularity { - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - - implicit val format: Format[Granularity] = - Jsonx.formatSealed[Granularity] + implicit val format: Format[Granularity] = enumFormat[Granularity] } case object All extends Granularity diff --git a/backend/app/models/Invitation.scala b/backend/app/models/Invitation.scala index 349602b..82cb7d5 100644 --- a/backend/app/models/Invitation.scala +++ b/backend/app/models/Invitation.scala @@ -27,8 +27,8 @@ import models.ProjectId.ProjectReference import models.UserId.UserReference import org.joda.time.DateTime import play.api.libs.json._ -import models.BaseFormat._ import play.api.libs.json.Format +import models.BaseFormat._ sealed trait Invitation extends BaseEntity[InvitationId] { val createDate: DateTime @@ -39,7 +39,7 @@ sealed trait Invitation extends BaseEntity[InvitationId] { } object Invitation { - implicit val format = + implicit val format: OFormat[Invitation] = derived.flat.oformat[Invitation](BaseFormat.defaultTypeFormat) } diff --git a/backend/app/models/InvitationOutcomeStatus.scala b/backend/app/models/InvitationOutcomeStatus.scala index 97376e4..53ca548 100644 --- a/backend/app/models/InvitationOutcomeStatus.scala +++ b/backend/app/models/InvitationOutcomeStatus.scala @@ -21,17 +21,14 @@ package models -import ai.x.play.json._ +import models.BaseFormat.enumFormat import play.api.libs.json.Format sealed trait InvitationOutcomeStatus object InvitationOutcomeStatus { - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - implicit val format: Format[InvitationOutcomeStatus] = - Jsonx.formatSealed[InvitationOutcomeStatus] + enumFormat[InvitationOutcomeStatus] } case object InvitationAccepted extends InvitationOutcomeStatus diff --git a/backend/app/models/InvitationStatusResponse.scala b/backend/app/models/InvitationStatusResponse.scala index f3f7548..07b536e 100644 --- a/backend/app/models/InvitationStatusResponse.scala +++ b/backend/app/models/InvitationStatusResponse.scala @@ -21,18 +21,13 @@ package models -import ai.x.play.json.Jsonx +import models.BaseFormat.enumFormat import play.api.libs.json.{Format, Json} sealed trait InvitationStatus object InvitationStatus { - - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - - implicit val format: Format[InvitationStatus] = - Jsonx.formatSealed[InvitationStatus] + implicit val format: Format[InvitationStatus] = enumFormat[InvitationStatus] } case object UnregisteredUser extends InvitationStatus diff --git a/backend/app/models/OrganisationRole.scala b/backend/app/models/OrganisationRole.scala index eaa7e17..51974ea 100644 --- a/backend/app/models/OrganisationRole.scala +++ b/backend/app/models/OrganisationRole.scala @@ -20,7 +20,7 @@ */ package models -import ai.x.play.json.Jsonx +import models.BaseFormat.enumFormat import play.api.libs.json.Format sealed trait OrganisationRole @@ -31,9 +31,5 @@ case object OrganisationAdministrator extends OrganisationRole object OrganisationRole { - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - - implicit val format: Format[OrganisationRole] = - Jsonx.formatSealed[OrganisationRole] + implicit val format: Format[OrganisationRole] = enumFormat[OrganisationRole] } diff --git a/backend/app/models/Tags.scala b/backend/app/models/Tags.scala index 17446d6..be99666 100644 --- a/backend/app/models/Tags.scala +++ b/backend/app/models/Tags.scala @@ -62,10 +62,11 @@ object SimpleTag { } object Tag { - implicit val tagWrites = + implicit val tagWrites: OWrites[Tag] = derived.flat.owrites[Tag](BaseFormat.defaultTypeFormat) - val defaultTagReads = derived.flat.reads[Tag](BaseFormat.defaultTypeFormat) - val tagReads = (JsPath \ "type").readNullable[String].flatMap { + private val defaultTagReads = + derived.flat.reads[Tag](BaseFormat.defaultTypeFormat) + private val tagReads = (JsPath \ "type").readNullable[String].flatMap { // by default map to "SimpleTag" to be compliant with old bookings based on TagId only case None => JsPath().read[String].map[Tag](tag => SimpleTag(TagId(tag))) diff --git a/backend/app/models/UserProject.scala b/backend/app/models/UserProject.scala index ca71e49..e2f141c 100644 --- a/backend/app/models/UserProject.scala +++ b/backend/app/models/UserProject.scala @@ -21,7 +21,7 @@ package models -import ai.x.play.json.Jsonx +import models.BaseFormat.enumFormat import models.OrganisationId.OrganisationReference import models.ProjectId.ProjectReference import play.api.libs.json._ @@ -33,11 +33,7 @@ case object ProjectMember extends ProjectRole case object ProjectAdministrator extends ProjectRole object ProjectRole { - - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - - implicit val format: Format[ProjectRole] = Jsonx.formatSealed[ProjectRole] + implicit val format: Format[ProjectRole] = enumFormat[ProjectRole] } case class UserProject( diff --git a/backend/app/models/UserRole.scala b/backend/app/models/UserRole.scala index ae3f5b4..70cbb24 100644 --- a/backend/app/models/UserRole.scala +++ b/backend/app/models/UserRole.scala @@ -21,7 +21,7 @@ package models -import ai.x.play.json.Jsonx +import models.BaseFormat.enumFormat import play.api.libs.json.Format sealed trait UserRole @@ -29,7 +29,5 @@ case object FreeUser extends UserRole case object Administrator extends UserRole object UserRole { - import ai.x.play.json.SingletonEncoder.simpleName - import ai.x.play.json.implicits.formatSingleton - implicit val format: Format[UserRole] = Jsonx.formatSealed[UserRole] + implicit val format: Format[UserRole] = enumFormat[UserRole] } diff --git a/backend/app/models/adapters/PersistedEventAdapter.scala b/backend/app/models/adapters/PersistedEventAdapter.scala index c3c9a09..8a7a663 100644 --- a/backend/app/models/adapters/PersistedEventAdapter.scala +++ b/backend/app/models/adapters/PersistedEventAdapter.scala @@ -29,14 +29,14 @@ import play.modules.reactivemongo.ReactiveMongoApi import repositories.{ProjectRepository, UserRepository} import scala.annotation.nowarn -import scala.concurrent.{Await, Awaitable} +import scala.concurrent.{Await, Awaitable, ExecutionContextExecutor} import scala.concurrent.duration.DurationInt import scala.language.postfixOps class PersistedEventAdapter(system: ExtendedActorSystem) extends ReadEventAdapter { - implicit val executionContext = system.dispatcher + implicit val executionContext: ExecutionContextExecutor = system.dispatcher lazy val allUsers = { val reactiveMongoApi = PlayAkkaExtension(system) diff --git a/backend/build.sbt b/backend/build.sbt index 3587237..fb67f81 100644 --- a/backend/build.sbt +++ b/backend/build.sbt @@ -19,7 +19,7 @@ swaggerDomainNameSpaces := Seq("models", "controllers") swaggerPrettyJson := true swaggerOutputTransformers += "core.swagger.SwaggerRenameModelClassesTransformer" -scalaVersion := "2.13.8" +scalaVersion := "2.13.12" buildInfoKeys := Seq[BuildInfoKey](organization, name, @@ -36,42 +36,43 @@ resolvers += "Tegonal releases".at( resolvers += "Sonatype OSS Releases".at( "https://oss.sonatype.org/content/repositories/releases") -val akkaVersion = "2.6.19" +val akkaVersion = "2.6.21" val reactiveMongoVersion = "1.0.10" val reactiveMongoPlayVersion = s"$reactiveMongoVersion-play28" -val silencerVersion = "1.4.3" -val monocleVersion = "2.0.0" -val playVersion = "2.8.15" +val playVersion = "2.8.20" libraryDependencies ++= Seq( ("org.reactivemongo" %% "play2-reactivemongo" % reactiveMongoPlayVersion) .exclude("org.apache.logging.log4j", "log4j-api"), - "com.github.scullxbones" %% "akka-persistence-mongo-rxmongo" % "3.0.8", + "com.github.scullxbones" %% "akka-persistence-mongo-rxmongo" % "3.1.2", "com.tegonal" %% "play-json-typedid" % "1.0.3", - "org.julienrf" %% "play-json-derived-codecs" % "10.0.2", - "com.typesafe.play" %% "play-json-joda" % "2.9.2", + "org.julienrf" %% "play-json-derived-codecs" % "10.1.0", + "com.typesafe.play" %% "play-json-joda" % "2.10.1", "com.google.inject" % "guice" % "5.1.0", "com.google.inject.extensions" % "guice-assistedinject" % "5.1.0", - "com.typesafe.play" %% "play-guice" % playVersion, // support more than 22 fields in case classes - "ai.x" %% "play-json-extensions" % "0.42.0", "com.typesafe.akka" %% "akka-persistence" % akkaVersion, "com.typesafe.akka" %% "akka-persistence-query" % akkaVersion, "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", // reativemongo based connector for persistent akka "org.mindrot" % "jbcrypt" % "0.4", - "de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % "3.4.5" % "test", + "de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % "4.9.2" % "test", "com.github.dnvriend" %% "akka-persistence-inmemory" % "2.5.15.2" % "test", "io.kontainers" %% "purecsv" % "1.3.10", - "com.chuusai" %% "shapeless" % "2.3.3", - "net.openhft" % "zero-allocation-hashing" % "0.15", + "com.chuusai" %% "shapeless" % "2.3.10", + "net.openhft" % "zero-allocation-hashing" % "0.16", // depend on this plugin to be able to provide custom OutputTransformer - "com.iheart" %% "play-swagger" % "0.10.6-PLAY2.8", + "io.github.play-swagger" %% "play-swagger" % "1.4.4", ehcache, ws, - specs2 % Test, - "org.webjars" % "swagger-ui" % "3.43.0" + specs2 % Test, + guice, + "org.webjars" % "swagger-ui" % "5.9.0" +) + +dependencyOverrides ++= Seq( + "com.fasterxml.jackson.module" % "jackson-module-scala_2.13" % "2.14.2", ) Test / javaOptions += "-Dconfig.file=conf/test.conf" diff --git a/backend/project/build.properties b/backend/project/build.properties index c8fcab5..563a014 100644 --- a/backend/project/build.properties +++ b/backend/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.2 +sbt.version=1.7.2 diff --git a/backend/project/plugins.sbt b/backend/project/plugins.sbt index 690f183..ca4eb28 100644 --- a/backend/project/plugins.sbt +++ b/backend/project/plugins.sbt @@ -2,21 +2,7 @@ resolvers += "Typesafe repository".at( "https://repo.typesafe.com/typesafe/releases/") // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.15") - -// web plugins - -addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.2") - -addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.1.2") - -addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.6") - -addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.10") - -addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.4") - -addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.1.2") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.20") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") @@ -24,7 +10,7 @@ addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") -addSbtPlugin("com.iheart" % "sbt-play-swagger" % "0.10.6-PLAY2.8") +addSbtPlugin("io.github.play-swagger" % "sbt-play-swagger" % "1.4.4") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0") diff --git a/backend/test/core/MockSystemServicesAware.scala b/backend/test/core/MockSystemServicesAware.scala index d9349d2..fad4a0e 100644 --- a/backend/test/core/MockSystemServicesAware.scala +++ b/backend/test/core/MockSystemServicesAware.scala @@ -44,30 +44,30 @@ class MockServicesProvider @Inject() (actorSystem: ActorSystem) class MockServices(actorSystem: ActorSystem) extends SystemServices { - val supervisor = + val supervisor: ActorRef = actorSystem.actorOf(LasiusSupervisorActor.props, "lasius-test-supervisor") val reactiveMongoApi: ReactiveMongoApi = mock[ReactiveMongoApi] override val supportTransaction: Boolean = false - implicit val system = actorSystem + implicit val system: ActorSystem = actorSystem override val materializer: Materializer = Materializer.matFromSystem - val systemUser = UserId() + val systemUser: UserId = UserId() override val systemUserReference: UserReference = EntityReference(systemUser, "system") override val systemSubject: Subject = Subject("", systemUserReference) - implicit val timeout = Timeout(5 seconds) // needed for `?` below - val duration = Duration.create(5, SECONDS) - val timeBookingViewService = TestProbe().ref + implicit val timeout: Timeout = Timeout(5 seconds) // needed for `?` below + val duration: Duration = Duration.create(5, SECONDS) + val timeBookingViewService: ActorRef = TestProbe().ref - val loginStateAggregate = TestProbe().ref + val loginStateAggregate: ActorRef = TestProbe().ref - val currentUserTimeBookingsViewService = TestProbe().ref - val currentOrganisationTimeBookingsView = TestProbe().ref - val latestUserTimeBookingsViewService = TestProbe().ref - val timeBookingStatisticsViewService = TestProbe().ref - val tagCache = TestProbe().ref - val pluginHandler = TestProbe().ref - val loginHandler = TestProbe().ref + val currentUserTimeBookingsViewService: ActorRef = TestProbe().ref + val currentOrganisationTimeBookingsView: ActorRef = TestProbe().ref + val latestUserTimeBookingsViewService: ActorRef = TestProbe().ref + val timeBookingStatisticsViewService: ActorRef = TestProbe().ref + val tagCache: ActorRef = TestProbe().ref + val pluginHandler: ActorRef = TestProbe().ref + val loginHandler: ActorRef = TestProbe().ref override def initialize(): Unit = {} } diff --git a/backend/test/models/JsonEnumSpec.scala b/backend/test/models/JsonEnumSpec.scala new file mode 100644 index 0000000..9ee84e1 --- /dev/null +++ b/backend/test/models/JsonEnumSpec.scala @@ -0,0 +1,121 @@ +/* + * + * Lasius - Open source time tracker for teams + * Copyright (c) Tegonal Genossenschaft (https://tegonal.com) + * + * This file is part of Lasius. + * + * Lasius is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Lasius is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Lasius. If not, see . + */ + +package models + +import org.specs2.mutable._ +import play.api.libs.json +import play.api.libs.json.JsSuccess + +class JsonEnumSpec extends Specification { + "Granularity format" should { + "be able to parse string values" in { + Granularity.format.reads(json.JsString("Day")) === JsSuccess(Day) + Granularity.format.reads(json.JsString("Week")) === JsSuccess(Week) + Granularity.format.reads(json.JsString("Month")) === JsSuccess(Month) + Granularity.format.reads(json.JsString("Year")) === JsSuccess(Year) + Granularity.format.reads(json.JsString("All")) === JsSuccess(All) + } + + "be able to write correct string values" in { + Granularity.format.writes(Day) === json.JsString("Day") + Granularity.format.writes(Week) === json.JsString("Week") + Granularity.format.writes(Month) === json.JsString("Month") + Granularity.format.writes(Year) === json.JsString("Year") + Granularity.format.writes(All) === json.JsString("All") + } + } + + "UserRole format" should { + "be able to parse string values" in { + UserRole.format.reads(json.JsString("FreeUser")) === JsSuccess(FreeUser) + UserRole.format.reads(json.JsString("Administrator")) === JsSuccess( + Administrator) + } + + "be able to write correct string values" in { + UserRole.format.writes(FreeUser) === json.JsString("FreeUser") + UserRole.format.writes(Administrator) === json.JsString("Administrator") + } + } + + "ProjectRole format" should { + "be able to parse string values" in { + ProjectRole.format.reads(json.JsString("ProjectMember")) === JsSuccess( + ProjectMember) + ProjectRole.format.reads( + json.JsString("ProjectAdministrator")) === JsSuccess( + ProjectAdministrator) + } + + "be able to write correct string values" in { + ProjectRole.format.writes(ProjectMember) === json.JsString( + "ProjectMember") + ProjectRole.format.writes(ProjectAdministrator) === json.JsString( + "ProjectAdministrator") + } + } + + "InvitationOutcomeStatus format" should { + "be able to parse string values" in { + InvitationOutcomeStatus.format.reads( + json.JsString("InvitationAccepted")) === JsSuccess(InvitationAccepted) + InvitationOutcomeStatus.format.reads( + json.JsString("InvitationDeclined")) === JsSuccess(InvitationDeclined) + } + + "be able to write correct string values" in { + InvitationOutcomeStatus.format.writes(InvitationAccepted) === json + .JsString("InvitationAccepted") + InvitationOutcomeStatus.format.writes(InvitationDeclined) === json + .JsString("InvitationDeclined") + } + } + + "InvitationStatus format" should { + "be able to parse string values" in { + InvitationStatus.format.reads( + json.JsString("UnregisteredUser")) === JsSuccess(UnregisteredUser) + InvitationStatus.format.reads( + json.JsString("InvitationOk")) === JsSuccess(InvitationOk) + } + + "be able to write correct string values" in { + InvitationStatus.format.writes(UnregisteredUser) === json + .JsString("UnregisteredUser") + InvitationStatus.format.writes(InvitationOk) === json + .JsString("InvitationOk") + } + } + + "UserRole format" should { + "be able to parse string values" in { + UserRole.format.reads(json.JsString("FreeUser")) === JsSuccess(FreeUser) + UserRole.format.reads(json.JsString("Administrator")) === JsSuccess( + Administrator) + } + + "be able to write correct string values" in { + UserRole.format.writes(FreeUser) === json.JsString("FreeUser") + UserRole.format.writes(Administrator) === json.JsString("Administrator") + } + } +} diff --git a/backend/test/mongo/MongoSetup.scala b/backend/test/mongo/MongoSetup.scala index 53b23ab..9cc55d1 100644 --- a/backend/test/mongo/MongoSetup.scala +++ b/backend/test/mongo/MongoSetup.scala @@ -22,72 +22,56 @@ package mongo import core.TestDBSupport -import de.flapdoodle.embed.mongo.config.{Defaults, MongodConfig, Net} +import de.flapdoodle.embed.mongo.config.Net import de.flapdoodle.embed.mongo.distribution.Version -import de.flapdoodle.embed.mongo.MongodStarter -import de.flapdoodle.embed.mongo.packageresolver.Command -import de.flapdoodle.embed.process.config.RuntimeConfig -import de.flapdoodle.embed.process.config.process.ProcessOutput -import de.flapdoodle.embed.process.io.{Processors, Slf4jLevel} -import de.flapdoodle.embed.process.runtime.Network +import de.flapdoodle.embed.mongo.transitions.Mongod +import de.flapdoodle.embed.process.io.{ProcessOutput, Processors, Slf4jLevel} +import de.flapdoodle.reverse.Transition +import de.flapdoodle.reverse.transitions.Start import org.slf4j.LoggerFactory import org.specs2.execute.{AsResult, Result} -import org.specs2.mutable.{Specification, SpecificationLike} +import org.specs2.mutable.SpecificationLike import org.specs2.specification.AroundEach +import play.api.Application import play.api.inject.guice.GuiceApplicationBuilder import play.modules.reactivemongo.ReactiveMongoApi import reactivemongo.api.bson.BSONObjectID import util.Awaitable -import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContext} +import scala.concurrent.ExecutionContext import scala.language.postfixOps object LazyMongo { - lazy val mongo = { + lazy val mongo: ReactiveMongoApi = { lazy val rnd = new scala.util.Random lazy val range = 12000 to 12999 lazy val port = range(rnd.nextInt(range length)) lazy val dbName = BSONObjectID.generate().stringify - implicit val executionContext = ExecutionContext.Implicits.global + lazy val logger = LoggerFactory.getLogger(getClass.getName) - lazy val mongodConfig = MongodConfig - .builder() - .version(Version.Main.PRODUCTION) - .net(new Net(port, Network.localhostIsIPv6())) - .build() - - lazy val logger = LoggerFactory.getLogger(getClass().getName()) + logger.info(s"Start mongo on port:$port") - lazy val processOutput = ProcessOutput - .builder() - .commands(Processors.logTo(logger, Slf4jLevel.TRACE)) - .error(Processors.logTo(logger, Slf4jLevel.TRACE)) - .output(Processors.named("[console>]", + val mongod = new Mongod() { + override def processOutput: Transition[ProcessOutput] = Start + .to(classOf[ProcessOutput]) + .initializedWith( + ProcessOutput.builder + .output( + Processors.named("[console>]", Processors.logTo(logger, Slf4jLevel.TRACE))) - .build(); - - val command = Command.MongoD + .error(Processors.logTo(logger, Slf4jLevel.TRACE)) + .commands(Processors.logTo(logger, Slf4jLevel.TRACE)) + .build) + .withTransitionLabel("create named console") - lazy val runtimeConfig: RuntimeConfig = Defaults - .runtimeConfigFor(command, logger) - .processOutput(processOutput) - .artifactStore( - Defaults - .extractedArtifactStoreFor(command) - .withDownloadConfig(Defaults.downloadConfigFor(command).build)) - .build - - lazy val runtime = MongodStarter.getInstance(runtimeConfig) - lazy val mongodExecutable = runtime.prepare(mongodConfig) - - logger.info(s"Start mongo on port:$port") - val proc = mongodExecutable.start - logger.info(s"Started mongo on port:$port:${proc.isProcessRunning()}") + override def net(): Transition[Net] = + Start.to(classOf[Net]).initializedWith(Net.defaults.withPort(port)) + } + mongod.start(Version.Main.V4_4) - implicit lazy val app = new GuiceApplicationBuilder() + implicit lazy val app: Application = new GuiceApplicationBuilder() .configure(Map( ("mongodb.uri", s"mongodb://localhost:$port/$dbName?w=majority&readConcernLevel=majority&maxPoolSize=1&rm.nbChannelsPerNode=1"), @@ -111,8 +95,9 @@ trait EmbedMongo with Awaitable with TestDBSupport { sequential => - implicit val executionContext = ExecutionContext.Implicits.global - override val reactiveMongoApi = LazyMongo.mongo + implicit val executionContext: ExecutionContext = + ExecutionContext.Implicits.global + override val reactiveMongoApi: ReactiveMongoApi = LazyMongo.mongo override protected def around[R](r: => R)(implicit evidence$1: AsResult[R]): Result = {