From e0607c424436c45eab7f0c73f24e26933da61016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Akif=20T=C3=BCt=C3=BCnc=C3=BC?= Date: Wed, 24 Aug 2016 16:52:07 +0300 Subject: [PATCH] Refactor Broom and Shovel, add tests for HtmlSanitizer and JsonErrorRepresenter --- .../muezzinapi/broom/BroomActor.scala | 170 ++++++++++-------- .../muezzinapi/shovel/ShovelActor.scala | 160 +++++++++-------- build.sbt | 5 +- .../utilities/HtmlSanitizerSpec.scala | 34 ++++ .../utilities/JsonErrorRepresenterSpec.scala | 90 ++++++++++ 5 files changed, 312 insertions(+), 147 deletions(-) create mode 100644 test/com/mehmetakiftutuncu/muezzinapi/utilities/HtmlSanitizerSpec.scala create mode 100644 test/com/mehmetakiftutuncu/muezzinapi/utilities/JsonErrorRepresenterSpec.scala diff --git a/app/com/mehmetakiftutuncu/muezzinapi/broom/BroomActor.scala b/app/com/mehmetakiftutuncu/muezzinapi/broom/BroomActor.scala index 6be7cbc..51fcb73 100644 --- a/app/com/mehmetakiftutuncu/muezzinapi/broom/BroomActor.scala +++ b/app/com/mehmetakiftutuncu/muezzinapi/broom/BroomActor.scala @@ -4,7 +4,7 @@ import java.time.{Duration, LocalDate, LocalDateTime} import java.util.concurrent.TimeUnit import akka.actor.Actor -import com.github.mehmetakiftutuncu.errors.{CommonError, Errors} +import com.github.mehmetakiftutuncu.errors.{CommonError, Errors, Maybe} import com.google.firebase.database.DatabaseReference.CompletionListener import com.google.firebase.database._ import com.mehmetakiftutuncu.muezzinapi.broom.BroomActor.Wipe @@ -16,7 +16,7 @@ import com.mehmetakiftutuncu.muezzinapi.utilities.{AbstractConf, Log, Logging, T import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.FiniteDuration import scala.concurrent.{Future, Promise} -import scala.util.{Failure, Success} +import scala.util.control.NonFatal class BroomActor(Conf: AbstractConf, FirebaseRealtimeDatabase: AbstractFirebaseRealtimeDatabase) extends Actor with Logging { @@ -28,105 +28,125 @@ class BroomActor(Conf: AbstractConf, Timer.start("broom") - val countryReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / "country" + collectReferencesToWipe(startDate).foreach { + maybeReferencesToWipe: Maybe[List[DatabaseReference]] => + if (maybeReferencesToWipe.hasErrors) { + val duration: Duration = Timer.stop("broom") - val referencesPromise: Promise[List[DatabaseReference]] = Promise[List[DatabaseReference]]() + Log.warn(s"Broom failed in ${duration.toMillis} ms with errors ${maybeReferencesToWipe.errors}!") + } else { + val referencesToWipe: List[DatabaseReference] = maybeReferencesToWipe.value - val valueEventListener: ValueEventListener = new ValueEventListener { - override def onCancelled(databaseError: DatabaseError): Unit = { - val exception: DatabaseException = databaseError.toException - val errors: Errors = Errors(CommonError.requestFailed.reason(exception.getMessage)) + wipeReferences(referencesToWipe).foreach { + wipeErrors: Errors => + val duration: Duration = Timer.stop("broom") - Log.error("Broom failed!", errors, exception) + if (wipeErrors.hasErrors) { + Log.warn(s"Broom failed in ${duration.toMillis} ms with errors $wipeErrors!") + } else { + Log.warn(s"Broom finished in ${duration.toMillis} ms! ${referencesToWipe.size} prayer times that are older than or equal to $startDate are wiped!") + } + } + } + } - referencesPromise.failure(exception) - } + case m @ _ => + Log.error("Broom failed!", Errors(CommonError.invalidData.reason("Received unknown message!").data(m.toString))) + } - override def onDataChange(dataSnapshot: DataSnapshot): Unit = { - import scala.collection.JavaConverters._ + private[broom] def collectReferencesToWipe(startDate: LocalDate): Future[Maybe[List[DatabaseReference]]] = { + val countryReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / "country" - val countrySnapshots: List[DataSnapshot] = dataSnapshot.getChildren.iterator().asScala.toList + val referencesPromise: Promise[List[DatabaseReference]] = Promise[List[DatabaseReference]]() - val prayerTimeReferencesToWipe: List[DatabaseReference] = countrySnapshots.flatMap { - countrySnapshot: DataSnapshot => - val citySnapshots: List[DataSnapshot] = (countrySnapshot / "city").getChildren.iterator().asScala.toList + val valueEventListener: ValueEventListener = new ValueEventListener { + override def onCancelled(databaseError: DatabaseError): Unit = { + referencesPromise.failure(databaseError.toException) + } - citySnapshots.flatMap { - citySnapshot: DataSnapshot => - val districtSnapshots: List[DataSnapshot] = (citySnapshot / "district").getChildren.iterator().asScala.toList + override def onDataChange(dataSnapshot: DataSnapshot): Unit = { + import scala.collection.JavaConverters._ - val prayerTimeSnapshotsToWipeForCity: List[DataSnapshot] = if (districtSnapshots.isEmpty) { - citySnapshot.getChildren.iterator().asScala.toList.takeWhile { - dateSnapshot: DataSnapshot => - val date: LocalDate = LocalDate.parse(dateSnapshot.getKey, PrayerTimesOfDay.dateFormatter) + val countrySnapshots: List[DataSnapshot] = dataSnapshot.getChildren.iterator().asScala.toList - date.isBefore(startDate) || date.isEqual(startDate) - } - } else { - districtSnapshots.flatMap { - districtSnapshot: DataSnapshot => - districtSnapshot.getChildren.iterator().asScala.toList.takeWhile { - dateSnapshot: DataSnapshot => - val date: LocalDate = LocalDate.parse(dateSnapshot.getKey, PrayerTimesOfDay.dateFormatter) + val prayerTimeReferencesToWipe: List[DatabaseReference] = countrySnapshots.flatMap { + countrySnapshot: DataSnapshot => + val citySnapshots: List[DataSnapshot] = (countrySnapshot / "city").getChildren.iterator().asScala.toList - date.isBefore(startDate) || date.isEqual(startDate) - } - } - } + citySnapshots.flatMap { + citySnapshot: DataSnapshot => + val districtSnapshots: List[DataSnapshot] = (citySnapshot / "district").getChildren.iterator().asScala.toList - prayerTimeSnapshotsToWipeForCity.map(_.getRef) - } - } + val prayerTimeSnapshotsToWipeForCity: List[DataSnapshot] = if (districtSnapshots.isEmpty) { + citySnapshot.getChildren.iterator().asScala.toList.takeWhile { + dateSnapshot: DataSnapshot => + val date: LocalDate = LocalDate.parse(dateSnapshot.getKey, PrayerTimesOfDay.dateFormatter) - referencesPromise.success(prayerTimeReferencesToWipe) + date.compareTo(startDate) < 1 + } + } else { + districtSnapshots.flatMap { + districtSnapshot: DataSnapshot => + districtSnapshot.getChildren.iterator().asScala.toList.takeWhile { + dateSnapshot: DataSnapshot => + val date: LocalDate = LocalDate.parse(dateSnapshot.getKey, PrayerTimesOfDay.dateFormatter) + + date.compareTo(startDate) < 1 + } + } + } + + prayerTimeSnapshotsToWipeForCity.map(_.getRef) + } } - } - countryReference.addValueEventListener(valueEventListener) + referencesPromise.success(prayerTimeReferencesToWipe) + } + } - val futureResult: Future[(Errors, Int)] = referencesPromise.future.flatMap { - references: List[DatabaseReference] => - countryReference.removeEventListener(valueEventListener) + countryReference.addValueEventListener(valueEventListener) - val wipeResultFutures: List[Future[Errors]] = references.map { - prayerTimeReference: DatabaseReference => - val promise: Promise[Errors] = Promise[Errors]() + referencesPromise.future.map { + references: List[DatabaseReference] => + countryReference.removeEventListener(valueEventListener) - prayerTimeReference.removeValue(new CompletionListener { - override def onComplete(databaseError: DatabaseError, databaseReference: DatabaseReference): Unit = { - val errors: Errors = if (databaseError != null) { - Errors(CommonError.database.reason(databaseError.toException.getMessage).data(databaseReference.getKey)) - } else { - Errors.empty - } + Maybe(references) + }.recover { + case NonFatal(t: Throwable) => + val errors: Errors = Errors(CommonError.database.reason(t.getMessage)) + Log.error("Broom failed to collect references to wipe from Firebase Realtime Database!", errors, t) - promise.success(errors) - } - }) + Maybe(errors) + } + } - promise.future - } + private[broom] def wipeReferences(referencesToWipe: List[DatabaseReference]): Future[Errors] = { + val wipeFutureResults: List[Future[Errors]] = referencesToWipe.map { + prayerTimeReference: DatabaseReference => + val promise: Promise[Errors] = Promise[Errors]() - Future.sequence(wipeResultFutures).map { - wipeResults: List[Errors] => - val wipeResult: Errors = wipeResults.foldLeft(Errors.empty)(_ ++ _) + prayerTimeReference.removeValue(new CompletionListener { + override def onComplete(databaseError: DatabaseError, databaseReference: DatabaseReference): Unit = { + val errors: Errors = if (databaseError != null) { + Errors(CommonError.database.reason(databaseError.toException.getMessage).data(databaseReference.getKey)) + } else { + Errors.empty + } - wipeResult -> references.size + promise.success(errors) } - } - - futureResult.onComplete { - case Success((errors: Errors, numberOfDeletedPrayerTimes: Int)) => - val duration: Duration = Timer.stop("broom") + }) - Log.warn(s"Broom finished in ${duration.toMillis} ms! $numberOfDeletedPrayerTimes prayer times that are older than or equal to $startDate are wiped${if (errors.hasErrors) " with errors " + errors else ""}!") + promise.future + } - case Failure(t: Throwable) => - Log.error("Broom failed!", t) - } + Future.sequence(wipeFutureResults).map(_.foldLeft(Errors.empty)(_ ++ _)).recover { + case NonFatal(t: Throwable) => + val errors: Errors = Errors(CommonError.database.reason(t.getMessage)) + Log.error("Broom failed to wipe collected references from Firebase Realtime Database!", errors, t) - case m @ _ => - Log.error("Broom failed!", Errors(CommonError.invalidData.reason("Received unknown message!").data(m.toString))) + errors + } } } diff --git a/app/com/mehmetakiftutuncu/muezzinapi/shovel/ShovelActor.scala b/app/com/mehmetakiftutuncu/muezzinapi/shovel/ShovelActor.scala index 681f42d..a53477a 100644 --- a/app/com/mehmetakiftutuncu/muezzinapi/shovel/ShovelActor.scala +++ b/app/com/mehmetakiftutuncu/muezzinapi/shovel/ShovelActor.scala @@ -5,8 +5,8 @@ import java.time.Duration import akka.actor.Actor import com.github.mehmetakiftutuncu.errors.{CommonError, Errors, Maybe} import com.google.firebase.database._ -import com.mehmetakiftutuncu.muezzinapi.data.{AbstractCache, AbstractFirebaseRealtimeDatabase} import com.mehmetakiftutuncu.muezzinapi.data.FirebaseRealtimeDatabase._ +import com.mehmetakiftutuncu.muezzinapi.data.{AbstractCache, AbstractFirebaseRealtimeDatabase} import com.mehmetakiftutuncu.muezzinapi.models.{Place, PrayerTimesOfDay} import com.mehmetakiftutuncu.muezzinapi.services.AbstractPrayerTimesService import com.mehmetakiftutuncu.muezzinapi.services.fetchers.AbstractPrayerTimesFetcherService @@ -15,7 +15,7 @@ import com.mehmetakiftutuncu.muezzinapi.utilities.{AbstractConf, Log, Logging, T import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{Future, Promise} -import scala.util.{Failure, Success} +import scala.util.control.NonFatal class ShovelActor(Cache: AbstractCache, Conf: AbstractConf, @@ -26,94 +26,112 @@ class ShovelActor(Cache: AbstractCache, case Dig => Timer.start("shovel") - val countryReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / "country" + collectPlacesToDig().foreach { + maybePlacesToDig: Maybe[List[Place]] => + if (maybePlacesToDig.hasErrors) { + val duration: Duration = Timer.stop("shovel") + + Log.warn(s"Shovel failed in ${duration.toMillis} ms with errors ${maybePlacesToDig.errors}!") + } else { + val placesToDig: List[Place] = maybePlacesToDig.value + + digPlaces(placesToDig).foreach { + digErrors: Errors => + val duration: Duration = Timer.stop("shovel") + + if (digErrors.hasErrors) { + Log.warn(s"Shovel failed in ${duration.toMillis} ms with errors $digErrors!") + } else { + Log.warn(s"Shovel finished in ${duration.toMillis} ms! Prayer times are fetched for ${placesToDig.size} places!") + } + } + } + } - val placesPromise: Promise[List[Place]] = Promise[List[Place]]() + case m @ _ => + Log.error("Shovel failed!", Errors(CommonError.invalidData.reason("Received unknown message!").data(m.toString))) + } - val valueEventListener: ValueEventListener = new ValueEventListener { - override def onCancelled(databaseError: DatabaseError): Unit = { - val exception: DatabaseException = databaseError.toException - val errors: Errors = Errors(CommonError.requestFailed.reason(exception.getMessage)) + private[shovel] def collectPlacesToDig(): Future[Maybe[List[Place]]] = { + val countryReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / "country" - Log.error("Shovel failed!", errors, exception) + val placesPromise: Promise[List[Place]] = Promise[List[Place]]() - placesPromise.failure(exception) - } + val valueEventListener: ValueEventListener = new ValueEventListener { + override def onCancelled(databaseError: DatabaseError): Unit = { + placesPromise.failure(databaseError.toException) + } - override def onDataChange(dataSnapshot: DataSnapshot): Unit = { - import scala.collection.JavaConverters._ + override def onDataChange(dataSnapshot: DataSnapshot): Unit = { + import scala.collection.JavaConverters._ - val countrySnapshots: List[DataSnapshot] = dataSnapshot.getChildren.iterator().asScala.toList + val countrySnapshots: List[DataSnapshot] = dataSnapshot.getChildren.iterator().asScala.toList - val places: List[Place] = countrySnapshots.flatMap { - countrySnapshot: DataSnapshot => - val citySnapshots: List[DataSnapshot] = (countrySnapshot / "city").getChildren.iterator().asScala.toList + val places: List[Place] = countrySnapshots.flatMap { + countrySnapshot: DataSnapshot => + val citySnapshots: List[DataSnapshot] = (countrySnapshot / "city").getChildren.iterator().asScala.toList - citySnapshots.flatMap { - citySnapshot: DataSnapshot => - val districtSnapshots: List[DataSnapshot] = (citySnapshot / "district").getChildren.iterator().asScala.toList + citySnapshots.flatMap { + citySnapshot: DataSnapshot => + val districtSnapshots: List[DataSnapshot] = (citySnapshot / "district").getChildren.iterator().asScala.toList - val placesForCity: List[Place] = if (districtSnapshots.isEmpty) { - List(Place(countrySnapshot.getKey.toInt, citySnapshot.getKey.toInt, None)) - } else { - districtSnapshots.map { - districtSnapshot: DataSnapshot => - Place(countrySnapshot.getKey.toInt, citySnapshot.getKey.toInt, Option(districtSnapshot.getKey.toInt)) - } + val placesForCity: List[Place] = if (districtSnapshots.isEmpty) { + List(Place(countrySnapshot.getKey.toInt, citySnapshot.getKey.toInt, None)) + } else { + districtSnapshots.map { + districtSnapshot: DataSnapshot => + Place(countrySnapshot.getKey.toInt, citySnapshot.getKey.toInt, Option(districtSnapshot.getKey.toInt)) } + } - placesForCity - } - } - - placesPromise.success(places) + placesForCity + } } - } - countryReference.addValueEventListener(valueEventListener) - - val futureResult: Future[(Errors, Int)] = placesPromise.future.flatMap { - places: List[Place] => - countryReference.removeEventListener(valueEventListener) - - val fetchResultFutures: List[Future[Errors]] = places.map { - place: Place => - PrayerTimesFetcherService.getPrayerTimes(place).flatMap { - maybePrayerTimes: Maybe[List[PrayerTimesOfDay]] => - if (maybePrayerTimes.hasErrors) { - Future.successful(maybePrayerTimes.errors) - } else { - val prayerTimes: List[PrayerTimesOfDay] = maybePrayerTimes.value - - PrayerTimesService.setPrayerTimesToFirebase(place, prayerTimes).map { - setErrors: Errors => - Cache.remove(s"/prayerTimes/${place.toPath}") - setErrors - } - } - } - } + placesPromise.success(places) + } + } - Future.sequence(fetchResultFutures).map { - fetchResults: List[Errors] => - val fetchResult: Errors = fetchResults.foldLeft(Errors.empty)(_ ++ _) + placesPromise.future.map { + places: List[Place] => + countryReference.removeEventListener(valueEventListener) - fetchResult -> places.size - } - } + Maybe(places) + }.recover { + case NonFatal(t: Throwable) => + val errors: Errors = Errors(CommonError.database.reason(t.getMessage)) + Log.error("Shovel failed to collect places to dig from Firebase Realtime Database!", errors, t) - futureResult.onComplete { - case Success((errors: Errors, numberOfPlaces: Int)) => - val duration: Duration = Timer.stop("shovel") + Maybe(errors) + } + } - Log.warn(s"Shovel finished in ${duration.toMillis} ms! Prayer times are fetched for $numberOfPlaces places${if (errors.hasErrors) " with errors " + errors else ""}!") + private[shovel] def digPlaces(placesToDig: List[Place]): Future[Errors] = { + val digFutureResults: List[Future[Errors]] = placesToDig.map { + place: Place => + PrayerTimesFetcherService.getPrayerTimes(place).flatMap { + maybePrayerTimes: Maybe[List[PrayerTimesOfDay]] => + if (maybePrayerTimes.hasErrors) { + Future.successful(maybePrayerTimes.errors) + } else { + val prayerTimes: List[PrayerTimesOfDay] = maybePrayerTimes.value + + PrayerTimesService.setPrayerTimesToFirebase(place, prayerTimes).map { + setErrors: Errors => + Cache.remove(s"/prayerTimes/${place.toPath}") + setErrors + } + } + } + } - case Failure(t: Throwable) => - Log.error("Shovel failed!", t) - } + Future.sequence(digFutureResults).map(_.foldLeft(Errors.empty)(_ ++ _)).recover { + case NonFatal(t: Throwable) => + val errors: Errors = Errors(CommonError.database.reason(t.getMessage)) + Log.error("Shovel failed to dig places collected from Firebase Realtime Database!", errors, t) - case m @ _ => - Log.error("Shovel failed!", Errors(CommonError.invalidData.reason("Received unknown message!").data(m.toString))) + errors + } } } diff --git a/build.sbt b/build.sbt index e48e45b..3ed4fa7 100644 --- a/build.sbt +++ b/build.sbt @@ -11,9 +11,12 @@ libraryDependencies ++= Seq( ws, "com.google.firebase" % "firebase-server-sdk" % "[3.0.0,)", "com.github.mehmetakiftutuncu" %% "errors" % "1.1", - "com.typesafe.akka" %% "akka-actor" % "2.4.9" + "com.typesafe.akka" %% "akka-actor" % "2.4.9", + "org.specs2" %% "specs2-core" % "3.8.4" % Test ) resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases" routesImport += "com.mehmetakiftutuncu.muezzinapi.utilities.OptionIntPathBindable._" + +scalacOptions in Test ++= Seq("-Yrangepos") diff --git a/test/com/mehmetakiftutuncu/muezzinapi/utilities/HtmlSanitizerSpec.scala b/test/com/mehmetakiftutuncu/muezzinapi/utilities/HtmlSanitizerSpec.scala new file mode 100644 index 0000000..1f4e478 --- /dev/null +++ b/test/com/mehmetakiftutuncu/muezzinapi/utilities/HtmlSanitizerSpec.scala @@ -0,0 +1,34 @@ +package com.mehmetakiftutuncu.muezzinapi.utilities + +import org.specs2.mutable.Specification + +class HtmlSanitizerSpec extends Specification { + "Sanitizing HTML" should { + "uppercase each word properly" in { + val html: String = "foo Bar baz" + val expectedResult: String = "Foo Bar Baz" + + val result: String = HtmlSanitizer.sanitizeHtml(html, replaceHtmlChars = false, upperCaseEachWord = true) + + result mustEqual expectedResult + } + + "replace HTML characters properly" in { + val html: String = "mehmet akif tütüncü" + val expectedResult: String = "mehmet akif tütüncü" + + val result: String = HtmlSanitizer.sanitizeHtml(html, replaceHtmlChars = true, upperCaseEachWord = false) + + result mustEqual expectedResult + } + + "replace HTML characters and uppercase each word properly" in { + val html: String = "mehmet akif tütüncü" + val expectedResult: String = "Mehmet Akif Tütüncü" + + val result: String = HtmlSanitizer.sanitizeHtml(html, replaceHtmlChars = true, upperCaseEachWord = true) + + result mustEqual expectedResult + } + } +} diff --git a/test/com/mehmetakiftutuncu/muezzinapi/utilities/JsonErrorRepresenterSpec.scala b/test/com/mehmetakiftutuncu/muezzinapi/utilities/JsonErrorRepresenterSpec.scala new file mode 100644 index 0000000..232d64b --- /dev/null +++ b/test/com/mehmetakiftutuncu/muezzinapi/utilities/JsonErrorRepresenterSpec.scala @@ -0,0 +1,90 @@ +package com.mehmetakiftutuncu.muezzinapi.utilities + +import com.github.mehmetakiftutuncu.errors.base.ErrorBase +import com.github.mehmetakiftutuncu.errors.{CommonError, Errors, SimpleError} +import org.specs2.mutable.Specification +import play.api.libs.json.{JsObject, JsValue, Json} + +class JsonErrorRepresenterSpec extends Specification { + "Representing an ErrorBase" should { + "represent SimpleError properly" in { + val error: SimpleError = SimpleError("foo") + val expectedResult: JsObject = Json.obj("name" -> "foo") + + val result: JsValue = JsonErrorRepresenter.represent(error, includeWhen = false) + + result mustEqual expectedResult + } + + "represent CommonError with name properly" in { + val error: CommonError = CommonError("foo") + val expectedResult: JsObject = Json.obj("name" -> "foo") + + val result: JsValue = JsonErrorRepresenter.represent(error, includeWhen = false) + + result mustEqual expectedResult + } + + "represent CommonError with name and reason properly" in { + val error: CommonError = CommonError("foo", "bar") + val expectedResult: JsObject = Json.obj("name" -> "foo", "reason" -> "bar") + + val result: JsValue = JsonErrorRepresenter.represent(error, includeWhen = false) + + result mustEqual expectedResult + } + + "represent CommonError with name and data properly" in { + val error: CommonError = CommonError("foo", data = "bar") + val expectedResult: JsObject = Json.obj("name" -> "foo", "data" -> "bar") + + val result: JsValue = JsonErrorRepresenter.represent(error, includeWhen = false) + + result mustEqual expectedResult + } + + "represent CommonError with name, reason and data properly" in { + val error: CommonError = CommonError("foo", "bar", "baz") + val expectedResult: JsObject = Json.obj("name" -> "foo", "reason" -> "bar", "data" -> "baz") + + val result: JsValue = JsonErrorRepresenter.represent(error, includeWhen = false) + + result mustEqual expectedResult + } + } + + br + + "Representing a representation as String" should { + "represent a CommonError with name, reason and data as String properly" in { + val error: CommonError = CommonError("foo", "bar", "baz") + val expectedResult: String = Json.obj("name" -> "foo", "reason" -> "bar", "data" -> "baz").toString() + + val result: String = JsonErrorRepresenter.asString(JsonErrorRepresenter.represent(error, includeWhen = false)) + + result mustEqual expectedResult + } + } + + br + + "Representing an ErrorBase list" should { + "represent empty ErrorBase list properly" in { + val errors: List[ErrorBase] = List.empty[ErrorBase] + val expectedResult: JsValue = Json.arr() + + val result: JsValue = JsonErrorRepresenter.represent(errors, includeWhen = false) + + result mustEqual expectedResult + } + + "represent non-empty ErrorBase list properly" in { + val errors: List[ErrorBase] = List(SimpleError("foo"), CommonError("foo", "bar", "baz")) + val expectedResult: JsValue = Json.arr(Json.obj("name" -> "foo"), Json.obj("name" -> "foo", "reason" -> "bar", "data" -> "baz")) + + val result: JsValue = JsonErrorRepresenter.represent(errors, includeWhen = false) + + result mustEqual expectedResult + } + } +}