From ed3cdd62bda373386568b1e3803f718d39a58d8f Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Mon, 31 Jan 2022 03:28:07 +0300 Subject: [PATCH] styling --- .../scala/me/arcanis/ffxivbis/ffxivbis.scala | 5 +- .../ffxivbis/http/AuthorizationProvider.scala | 3 +- .../ffxivbis/http/api/v1/HttpHandler.scala | 8 +- .../ffxivbis/http/api/v1/json/ApiAction.scala | 1 + .../ffxivbis/http/helpers/BiSHelper.scala | 3 +- .../http/helpers/BisProviderHelper.scala | 5 +- .../ffxivbis/http/helpers/LootHelper.scala | 3 +- .../ffxivbis/http/helpers/PlayerHelper.scala | 3 +- .../ffxivbis/http/helpers/UserHelper.scala | 4 +- .../messages/BiSProviderMessage.scala | 7 +- ...ntolMessage.scala => ControlMessage.scala} | 11 +- .../ffxivbis/messages/DatabaseMessage.scala | 203 +++++++++--------- .../me/arcanis/ffxivbis/models/BiS.scala | 8 +- .../me/arcanis/ffxivbis/models/Job.scala | 83 +++---- .../me/arcanis/ffxivbis/models/Loot.scala | 4 +- .../me/arcanis/ffxivbis/models/Party.scala | 3 +- .../ffxivbis/models/PartyDescription.scala | 5 +- .../me/arcanis/ffxivbis/models/Piece.scala | 169 +++++++-------- .../arcanis/ffxivbis/models/PieceType.scala | 11 +- .../me/arcanis/ffxivbis/models/Player.scala | 4 +- .../me/arcanis/ffxivbis/models/PlayerId.scala | 4 +- .../models/PlayerIdWithCounters.scala | 10 +- .../me/arcanis/ffxivbis/models/User.scala | 1 + .../ffxivbis/service/PartyService.scala | 17 +- .../ffxivbis/service/bis/BisProvider.scala | 13 +- .../arcanis/ffxivbis/service/bis/XivApi.scala | 15 +- .../ffxivbis/service/bis/parser/Parser.scala | 2 +- .../service/bis/parser/impl/Ariyala.scala | 2 +- .../service/bis/parser/impl/Etro.scala | 2 +- .../database/impl/DatabaseBiSHandler.scala | 3 +- .../service/database/impl/DatabaseImpl.scala | 3 +- .../database/impl/DatabaseLootHandler.scala | 3 +- .../database/impl/DatabasePartyHandler.scala | 3 +- .../database/impl/DatabaseUserHandler.scala | 3 +- .../ffxivbis/storage/DatabaseProfile.scala | 3 +- .../me/arcanis/ffxivbis/ApplicationTest.scala | 16 ++ .../arcanis/ffxivbis/ConfigurationTest.scala | 20 ++ .../scala/me/arcanis/ffxivbis/Fixtures.scala | 89 ++++---- .../scala/me/arcanis/ffxivbis/Settings.scala | 9 +- .../ffxivbis/http/AuthorizationTest.scala | 4 +- .../http/api/v1/BiSEndpointTest.scala | 20 +- .../http/api/v1/HttpHandlerTest.scala | 78 +++++++ .../http/api/v1/LootEndpointTest.scala | 2 +- .../http/api/v1/PartyEndpointTest.scala | 2 +- .../http/api/v1/PlayerEndpointTest.scala | 3 +- .../me/arcanis/ffxivbis/models/BiSTest.scala | 2 +- .../arcanis/ffxivbis/models/PieceTest.scala | 8 +- .../ffxivbis/service/LootSelectorTest.scala | 24 +-- .../service/bis/BisProviderTest.scala | 2 +- .../database/DatabaseBiSHandlerTest.scala | 4 +- .../database/DatabaseLootHandlerTest.scala | 3 +- .../database/DatabasePartyHandlerTest.scala | 2 +- .../database/DatabaseUserHandlerTest.scala | 3 +- .../me/arcanis/ffxivbis/utils/Compare.scala | 1 + .../arcanis/ffxivbis/utils/Converters.scala | 1 - .../ffxivbis/utils/ImplicitsTest.scala | 43 ++++ 56 files changed, 573 insertions(+), 390 deletions(-) rename src/main/scala/me/arcanis/ffxivbis/messages/{ContolMessage.scala => ControlMessage.scala} (52%) create mode 100644 src/test/scala/me/arcanis/ffxivbis/ApplicationTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/ConfigurationTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandlerTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/utils/ImplicitsTest.scala diff --git a/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala b/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala index 15db400..45ff142 100644 --- a/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala +++ b/src/main/scala/me/arcanis/ffxivbis/ffxivbis.scala @@ -10,8 +10,7 @@ package me.arcanis.ffxivbis import akka.actor.typed.ActorSystem -object ffxivbis { +object ffxivbis extends App { - def main(args: Array[String]): Unit = - ActorSystem[Nothing](Application(), "ffxivbis", Configuration.load()) + ActorSystem[Nothing](Application(), "ffxivbis", Configuration.load()) } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala index 384bc4c..849e4ff 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala @@ -13,7 +13,8 @@ import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} import com.typesafe.config.Config -import me.arcanis.ffxivbis.messages.{GetUser, Message} +import me.arcanis.ffxivbis.messages.DatabaseMessage.GetUser +import me.arcanis.ffxivbis.messages.Message import me.arcanis.ffxivbis.models.{Permission, User} import java.util.concurrent.TimeUnit diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala index 76229d2..d3392a0 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala @@ -16,13 +16,15 @@ import com.typesafe.scalalogging.StrictLogging import me.arcanis.ffxivbis.http.api.v1.json._ import spray.json._ +import scala.util.control.NonFatal + trait HttpHandler extends StrictLogging { this: JsonSupport => def exceptionHandler: ExceptionHandler = ExceptionHandler { - case ex: IllegalArgumentException => - complete(StatusCodes.BadRequest, ErrorModel(ex.getMessage)) + case exception: IllegalArgumentException => + complete(StatusCodes.BadRequest, ErrorModel(exception.getMessage)) - case other: Exception => + case NonFatal(other) => logger.error("exception during request completion", other) complete(StatusCodes.InternalServerError, ErrorModel("unknown server error")) } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala index 6051720..cd0a1ad 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ApiAction.scala @@ -9,5 +9,6 @@ package me.arcanis.ffxivbis.http.api.v1.json object ApiAction extends Enumeration { + val add, remove = Value } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala index 3a8131d..0cdcd1d 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BiSHelper.scala @@ -12,7 +12,8 @@ import akka.actor.typed.scaladsl.AskPattern.Askable import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout import me.arcanis.ffxivbis.http.api.v1.json.ApiAction -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage._ +import me.arcanis.ffxivbis.messages.Message import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId} import scala.concurrent.{ExecutionContext, Future} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala index 5322132..01d2e30 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/BisProviderHelper.scala @@ -11,7 +11,8 @@ package me.arcanis.ffxivbis.http.helpers import akka.actor.typed.scaladsl.AskPattern.Askable import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout -import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS} +import me.arcanis.ffxivbis.messages.BiSProviderMessage +import me.arcanis.ffxivbis.messages.BiSProviderMessage._ import me.arcanis.ffxivbis.models.{BiS, Job} import scala.concurrent.Future @@ -20,6 +21,6 @@ trait BisProviderHelper { def provider: ActorRef[BiSProviderMessage] - def downloadBiS(link: String, job: Job.Job)(implicit timeout: Timeout, scheduler: Scheduler): Future[BiS] = + def downloadBiS(link: String, job: Job)(implicit timeout: Timeout, scheduler: Scheduler): Future[BiS] = provider.ask(DownloadBiS(link, job, _)) } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala index aa93be7..fb2dfd3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/LootHelper.scala @@ -12,7 +12,8 @@ import akka.actor.typed.scaladsl.AskPattern.Askable import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout import me.arcanis.ffxivbis.http.api.v1.json.ApiAction -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage._ +import me.arcanis.ffxivbis.messages.Message import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId, PlayerIdWithCounters} import scala.concurrent.{ExecutionContext, Future} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala index 496c63f..a19ad30 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/PlayerHelper.scala @@ -12,7 +12,8 @@ import akka.actor.typed.scaladsl.AskPattern.Askable import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout import me.arcanis.ffxivbis.http.api.v1.json.ApiAction -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage._ +import me.arcanis.ffxivbis.messages.Message import me.arcanis.ffxivbis.models.{PartyDescription, Player, PlayerId} import scala.concurrent.{ExecutionContext, Future} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala index 5b330a1..418ba53 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/helpers/UserHelper.scala @@ -11,7 +11,9 @@ package me.arcanis.ffxivbis.http.helpers import akka.actor.typed.scaladsl.AskPattern.Askable import akka.actor.typed.{ActorRef, Scheduler} import akka.util.Timeout -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.ControlMessage.GetNewPartyId +import me.arcanis.ffxivbis.messages.DatabaseMessage._ +import me.arcanis.ffxivbis.messages.Message import me.arcanis.ffxivbis.models.User import scala.concurrent.Future diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala index 703c9fc..138202d 100644 --- a/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala +++ b/src/main/scala/me/arcanis/ffxivbis/messages/BiSProviderMessage.scala @@ -13,7 +13,10 @@ import me.arcanis.ffxivbis.models.{BiS, Job} sealed trait BiSProviderMessage -case class DownloadBiS(link: String, job: Job.Job, replyTo: ActorRef[BiS]) extends BiSProviderMessage { +object BiSProviderMessage { - require(link.nonEmpty && link.trim == link, "Link must be not empty and contain no spaces") + case class DownloadBiS(link: String, job: Job, replyTo: ActorRef[BiS]) extends BiSProviderMessage { + + require(link.nonEmpty && link.trim == link, "Link must be not empty and contain no spaces") + } } diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/ControlMessage.scala similarity index 52% rename from src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala rename to src/main/scala/me/arcanis/ffxivbis/messages/ControlMessage.scala index cdceda5..a148fc5 100644 --- a/src/main/scala/me/arcanis/ffxivbis/messages/ContolMessage.scala +++ b/src/main/scala/me/arcanis/ffxivbis/messages/ControlMessage.scala @@ -11,8 +11,13 @@ package me.arcanis.ffxivbis.messages import akka.actor.typed.ActorRef import me.arcanis.ffxivbis.models.Party -case class ForgetParty(partyId: String) extends Message +sealed trait ControlMessage extends Message -case class GetNewPartyId(replyTo: ActorRef[String]) extends Message +object ControlMessage { -case class StoreParty(partyId: String, party: Party) extends Message + case class ForgetParty(partyId: String) extends ControlMessage + + case class GetNewPartyId(replyTo: ActorRef[String]) extends ControlMessage + + case class StoreParty(partyId: String, party: Party) extends ControlMessage +} diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala index 3e69445..33b5aeb 100644 --- a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala +++ b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala @@ -19,105 +19,108 @@ sealed trait DatabaseMessage extends Message { def isReadOnly: Boolean } -// bis handler -trait BisDatabaseMessage extends DatabaseMessage +object DatabaseMessage { -case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) - extends BisDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -// loot handler -trait LootDatabaseMessage extends DatabaseMessage - -case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) - extends LootDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) - extends LootDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class RemovePieceFrom(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) - extends LootDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -case class SuggestLoot(partyId: String, piece: Piece, replyTo: ActorRef[LootSelector.LootSelectorResult]) - extends LootDatabaseMessage { - override val isReadOnly: Boolean = true -} - -// party handler -trait PartyDatabaseMessage extends DatabaseMessage - -case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override val partyId: String = player.partyId - override val isReadOnly: Boolean = false -} - -case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends PartyDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends PartyDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends PartyDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = true -} - -case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override val partyId: String = playerId.partyId - override val isReadOnly: Boolean = false -} - -case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override val partyId: String = partyDescription.partyId - override val isReadOnly: Boolean = false -} - -// user handler -trait UserDatabaseMessage extends DatabaseMessage - -case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { - override val partyId: String = user.partyId - override val isReadOnly: Boolean = false -} - -case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends UserDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends UserDatabaseMessage { - override val isReadOnly: Boolean = true -} - -case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends UserDatabaseMessage { - override val isReadOnly: Boolean = true + // bis handler + trait BisDatabaseMessage extends DatabaseMessage + + case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) + extends BisDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + // loot handler + trait LootDatabaseMessage extends DatabaseMessage + + case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) + extends LootDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) + extends LootDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class RemovePieceFrom(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) + extends LootDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + case class SuggestLoot(partyId: String, piece: Piece, replyTo: ActorRef[LootSelector.LootSelectorResult]) + extends LootDatabaseMessage { + override val isReadOnly: Boolean = true + } + + // party handler + trait PartyDatabaseMessage extends DatabaseMessage + + case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { + override val partyId: String = player.partyId + override val isReadOnly: Boolean = false + } + + case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends PartyDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends PartyDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends PartyDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = true + } + + case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false + } + + case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { + override val partyId: String = partyDescription.partyId + override val isReadOnly: Boolean = false + } + + // user handler + trait UserDatabaseMessage extends DatabaseMessage + + case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { + override val partyId: String = user.partyId + override val isReadOnly: Boolean = false + } + + case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true + } + + case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true + } } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala b/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala index 7eb3e29..efb123e 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/BiS.scala @@ -11,14 +11,14 @@ package me.arcanis.ffxivbis.models case class BiS(pieces: Seq[Piece]) { def hasPiece(piece: Piece): Boolean = piece match { - case upgrade: PieceUpgrade => upgrades.contains(upgrade) + case upgrade: Piece.PieceUpgrade => upgrades.contains(upgrade) case _ => pieces.contains(piece) } - def upgrades: Map[PieceUpgrade, Int] = + def upgrades: Map[Piece.PieceUpgrade, Int] = pieces .groupBy(_.upgrade) - .foldLeft(Map.empty[PieceUpgrade, Int]) { + .foldLeft(Map.empty[Piece.PieceUpgrade, Int]) { case (acc, (Some(k), v)) => acc + (k -> v.size) case (acc, _) => acc } @@ -43,5 +43,5 @@ case class BiS(pieces: Seq[Piece]) { object BiS { - def empty: BiS = BiS(Seq.empty) + val empty: BiS = BiS(Seq.empty) } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala index df4a4aa..61a0f2a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Job.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Job.scala @@ -8,6 +8,26 @@ */ package me.arcanis.ffxivbis.models +sealed trait Job extends Equals { + + def leftSide: Job.LeftSide + + def rightSide: Job.RightSide + + // conversion to string to avoid recursion + override def canEqual(that: Any): Boolean = that.isInstanceOf[Job] + + override def equals(obj: Any): Boolean = { + def equality(objRepr: String): Boolean = objRepr match { + case _ if objRepr == Job.AnyJob.toString => true + case _ if this.toString == Job.AnyJob.toString => true + case _ => this.toString == objRepr + } + + canEqual(obj) && equality(obj.toString) + } +} + object Job { sealed trait RightSide @@ -26,54 +46,38 @@ object Job { object BodyTanks extends LeftSide object BodyRanges extends LeftSide - sealed trait Job extends Equals { - - def leftSide: LeftSide - - def rightSide: RightSide - - // conversion to string to avoid recursion - override def canEqual(that: Any): Boolean = that.isInstanceOf[Job] - - override def equals(obj: Any): Boolean = { - def equality(objRepr: String): Boolean = objRepr match { - case _ if objRepr == AnyJob.toString => true - case _ if this.toString == AnyJob.toString => true - case _ => this.toString == objRepr - } - - canEqual(obj) && equality(obj.toString) - } - } - case object AnyJob extends Job { - val leftSide: LeftSide = null - val rightSide: RightSide = null + override val leftSide: LeftSide = null + override val rightSide: RightSide = null } trait Casters extends Job { - val leftSide: LeftSide = BodyCasters - val rightSide: RightSide = AccessoriesInt + override val leftSide: LeftSide = BodyCasters + override val rightSide: RightSide = AccessoriesInt } trait Healers extends Job { - val leftSide: LeftSide = BodyHealers - val rightSide: RightSide = AccessoriesMnd + override val leftSide: LeftSide = BodyHealers + override val rightSide: RightSide = AccessoriesMnd } trait Mnks extends Job { - val leftSide: LeftSide = BodyMnks - val rightSide: RightSide = AccessoriesStr + override val leftSide: LeftSide = BodyMnks + override val rightSide: RightSide = AccessoriesStr } trait Drgs extends Job { - val leftSide: LeftSide = BodyDrgs - val rightSide: RightSide = AccessoriesStr + override val leftSide: LeftSide = BodyDrgs + override val rightSide: RightSide = AccessoriesStr + } + trait Nins extends Job { + override val leftSide: LeftSide = BodyNins + override val rightSide: RightSide = AccessoriesDex } trait Tanks extends Job { - val leftSide: LeftSide = BodyTanks - val rightSide: RightSide = AccessoriesVit + override val leftSide: LeftSide = BodyTanks + override val rightSide: RightSide = AccessoriesVit } trait Ranges extends Job { - val leftSide: LeftSide = BodyRanges - val rightSide: RightSide = AccessoriesDex + override val leftSide: LeftSide = BodyRanges + override val rightSide: RightSide = AccessoriesDex } case object PLD extends Tanks @@ -89,10 +93,7 @@ object Job { case object MNK extends Mnks case object DRG extends Drgs case object RPR extends Drgs - case object NIN extends Job { - val leftSide: LeftSide = BodyNins - val rightSide: RightSide = AccessoriesDex - } + case object NIN extends Nins case object SAM extends Mnks case object BRD extends Ranges @@ -103,11 +104,11 @@ object Job { case object SMN extends Casters case object RDM extends Casters - lazy val available: Seq[Job] = + val available: Seq[Job] = Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, SGE, MNK, DRG, RPR, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM) - lazy val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob) + val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob) - def withName(job: String): Job.Job = + def withName(job: String): Job = availableWithAnyJob.find(_.toString.equalsIgnoreCase(job)) match { case Some(value) => value case None if job.isEmpty => AnyJob diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala index 589b053..1abadde 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Loot.scala @@ -12,7 +12,5 @@ import java.time.Instant case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) { - def isFreeLootToString: String = if (isFreeLoot) "yes" else "no" - - def isFreeLootToInt: Int = if (isFreeLoot) 1 else 0 + lazy val isFreeLootToInt: Int = if (isFreeLoot) 1 else 0 } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala index 62a4330..40507b4 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Party.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Party.scala @@ -14,6 +14,7 @@ import me.arcanis.ffxivbis.service.LootSelector import scala.jdk.CollectionConverters._ import scala.util.Random +import scala.util.control.NonFatal case class Party(partyDescription: PartyDescription, rules: Seq[String], players: Map[PlayerId, Player]) extends StrictLogging { @@ -29,7 +30,7 @@ case class Party(partyDescription: PartyDescription, rules: Seq[String], players require(player.partyId == partyDescription.partyId, "player must belong to this party") copy(players = players + (player.playerId -> player)) } catch { - case exception: Exception => + case NonFatal(exception) => logger.error("cannot add player", exception) this } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala index a138856..4855032 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/PartyDescription.scala @@ -8,10 +8,7 @@ */ package me.arcanis.ffxivbis.models -case class PartyDescription(partyId: String, partyAlias: Option[String]) { - - def alias: String = partyAlias.getOrElse(partyId) -} +case class PartyDescription(partyId: String, partyAlias: Option[String]) object PartyDescription { diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala b/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala index 87d3af0..59469be 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Piece.scala @@ -10,20 +10,20 @@ package me.arcanis.ffxivbis.models sealed trait Piece extends Equals { - def pieceType: PieceType.PieceType + def pieceType: PieceType - def job: Job.Job + def job: Job def piece: String - def withJob(other: Job.Job): Piece + def withJob(other: Job): Piece - def upgrade: Option[PieceUpgrade] = { + def upgrade: Option[Piece.PieceUpgrade] = { val isTome = pieceType == PieceType.Tome Some(this).collect { - case _: PieceAccessory if isTome => AccessoryUpgrade - case _: PieceBody if isTome => BodyUpgrade - case _: PieceWeapon if isTome => WeaponUpgrade + case _: Piece.PieceAccessory if isTome => Piece.AccessoryUpgrade + case _: Piece.PieceBody if isTome => Piece.BodyUpgrade + case _: Piece.PieceWeapon if isTome => Piece.WeaponUpgrade } } @@ -31,83 +31,84 @@ sealed trait Piece extends Equals { def strictEqual(obj: Any): Boolean = equals(obj) } -trait PieceAccessory extends Piece -trait PieceBody extends Piece -trait PieceUpgrade extends Piece { - val pieceType: PieceType.PieceType = PieceType.Tome - val job: Job.Job = Job.AnyJob - def withJob(other: Job.Job): Piece = this -} -trait PieceWeapon extends Piece - -case class Weapon(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceWeapon { - val piece: String = "weapon" - def withJob(other: Job.Job): Piece = copy(job = other) -} - -case class Head(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody { - val piece: String = "head" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Body(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody { - val piece: String = "body" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Hands(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody { - val piece: String = "hands" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Legs(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody { - val piece: String = "legs" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Feet(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceBody { - val piece: String = "feet" - def withJob(other: Job.Job): Piece = copy(job = other) -} - -case class Ears(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory { - val piece: String = "ears" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Neck(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory { - val piece: String = "neck" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Wrist(override val pieceType: PieceType.PieceType, override val job: Job.Job) extends PieceAccessory { - val piece: String = "wrist" - def withJob(other: Job.Job): Piece = copy(job = other) -} -case class Ring( - override val pieceType: PieceType.PieceType, - override val job: Job.Job, - override val piece: String = "ring" -) extends PieceAccessory { - def withJob(other: Job.Job): Piece = copy(job = other) - - override def equals(obj: Any): Boolean = obj match { - case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job) - case _ => false - } - - override def strictEqual(obj: Any): Boolean = obj match { - case ring: Ring => equals(obj) && (ring.piece == this.piece) - case _ => false - } -} - -case object AccessoryUpgrade extends PieceUpgrade { - val piece: String = "accessory upgrade" -} -case object BodyUpgrade extends PieceUpgrade { - val piece: String = "body upgrade" -} -case object WeaponUpgrade extends PieceUpgrade { - val piece: String = "weapon upgrade" -} - object Piece { - def apply(piece: String, pieceType: PieceType.PieceType, job: Job.Job = Job.AnyJob): Piece = + + trait PieceAccessory extends Piece + trait PieceBody extends Piece + trait PieceUpgrade extends Piece { + override val pieceType: PieceType = PieceType.Tome + override val job: Job = Job.AnyJob + override def withJob(other: Job): Piece = this + } + trait PieceWeapon extends Piece + + case class Weapon(override val pieceType: PieceType, override val job: Job) extends PieceWeapon { + override val piece: String = "weapon" + override def withJob(other: Job): Piece = copy(job = other) + } + + case class Head(override val pieceType: PieceType, override val job: Job) extends PieceBody { + override val piece: String = "head" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Body(override val pieceType: PieceType, override val job: Job) extends PieceBody { + override val piece: String = "body" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Hands(override val pieceType: PieceType, override val job: Job) extends PieceBody { + override val piece: String = "hands" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Legs(override val pieceType: PieceType, override val job: Job) extends PieceBody { + override val piece: String = "legs" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Feet(override val pieceType: PieceType, override val job: Job) extends PieceBody { + override val piece: String = "feet" + override def withJob(other: Job): Piece = copy(job = other) + } + + case class Ears(override val pieceType: PieceType, override val job: Job) extends PieceAccessory { + override val piece: String = "ears" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Neck(override val pieceType: PieceType, override val job: Job) extends PieceAccessory { + override val piece: String = "neck" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Wrist(override val pieceType: PieceType, override val job: Job) extends PieceAccessory { + override val piece: String = "wrist" + override def withJob(other: Job): Piece = copy(job = other) + } + case class Ring( + override val pieceType: PieceType, + override val job: Job, + override val piece: String = "ring" + ) extends PieceAccessory { + override def withJob(other: Job): Piece = copy(job = other) + + override def equals(obj: Any): Boolean = obj match { + case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job) + case _ => false + } + + override def strictEqual(obj: Any): Boolean = obj match { + case ring: Ring => equals(obj) && (ring.piece == this.piece) + case _ => false + } + } + + case object AccessoryUpgrade extends PieceUpgrade { + override val piece: String = "accessory upgrade" + } + case object BodyUpgrade extends PieceUpgrade { + override val piece: String = "body upgrade" + } + case object WeaponUpgrade extends PieceUpgrade { + override val piece: String = "weapon upgrade" + } + + def apply(piece: String, pieceType: PieceType, job: Job = Job.AnyJob): Piece = piece.toLowerCase match { case "weapon" => Weapon(pieceType, job) case "head" => Head(pieceType, job) @@ -125,7 +126,7 @@ object Piece { case other => throw new Error(s"Unknown item type $other") } - lazy val available: Seq[String] = Seq( + val available: Seq[String] = Seq( "weapon", "head", "body", diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala b/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala index bb1f7ef..c32ad76 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/PieceType.scala @@ -8,17 +8,16 @@ */ package me.arcanis.ffxivbis.models +sealed trait PieceType + object PieceType { - sealed trait PieceType - - case object Crafted extends PieceType - case object Tome extends PieceType case object Savage extends PieceType + case object Tome extends PieceType + case object Crafted extends PieceType case object Artifact extends PieceType - lazy val available: Seq[PieceType] = - Seq(Crafted, Tome, Savage, Artifact) + val available: Seq[PieceType] = Seq(Savage, Tome, Crafted, Artifact) def withName(pieceType: String): PieceType = available.find(_.toString.equalsIgnoreCase(pieceType)) match { diff --git a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala index 6faeb31..1efae6a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/Player.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/Player.scala @@ -11,7 +11,7 @@ package me.arcanis.ffxivbis.models case class Player( id: Long, partyId: String, - job: Job.Job, + job: Job, nick: String, bis: BiS, loot: Seq[Loot], @@ -51,7 +51,7 @@ case class Player( piece match { case None => false case Some(p) if !bis.hasPiece(p) => false - case Some(p: PieceUpgrade) => bis.upgrades(p) > lootCount(piece) + case Some(p: Piece.PieceUpgrade) => bis.upgrades(p) > lootCount(piece) case Some(_) => lootCount(piece) == 0 } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala b/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala index d7d1821..cb9edf6 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/PlayerId.scala @@ -13,14 +13,14 @@ import scala.util.matching.Regex trait PlayerIdBase { - def job: Job.Job + def job: Job def nick: String override def toString: String = s"$nick ($job)" } -case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase +case class PlayerId(partyId: String, job: Job, nick: String) extends PlayerIdBase object PlayerId { diff --git a/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala b/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala index ba1a1a6..ef93c08 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/PlayerIdWithCounters.scala @@ -10,7 +10,7 @@ package me.arcanis.ffxivbis.models case class PlayerIdWithCounters( partyId: String, - job: Job.Job, + job: Job, nick: String, isRequired: Boolean, priority: Int, @@ -24,8 +24,6 @@ case class PlayerIdWithCounters( def gt(that: PlayerIdWithCounters, orderBy: Seq[String]): Boolean = withCounters(orderBy) > that.withCounters(orderBy) - def isRequiredToString: String = if (isRequired) "yes" else "no" - def playerId: PlayerId = PlayerId(partyId, job, nick) private val counters: Map[String, Int] = Map( @@ -47,13 +45,13 @@ object PlayerIdWithCounters { def >(that: PlayerCountersComparator): Boolean = { @scala.annotation.tailrec - def compareLists(left: List[Int], right: List[Int]): Boolean = + def compare(left: Seq[Int], right: Seq[Int]): Boolean = (left, right) match { - case (hl :: tl, hr :: tr) => if (hl == hr) compareLists(tl, tr) else hl > hr + case (hl :: tl, hr :: tr) => if (hl == hr) compare(tl, tr) else hl > hr case (_ :: _, Nil) => true case (_, _) => false } - compareLists(values.toList, that.values.toList) + compare(values, that.values) } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/models/User.scala b/src/main/scala/me/arcanis/ffxivbis/models/User.scala index 17caacc..5e38475 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/User.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/User.scala @@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.models import org.mindrot.jbcrypt.BCrypt object Permission extends Enumeration { + val get, post, admin = Value } diff --git a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala index d7eb595..1549c82 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala @@ -36,23 +36,24 @@ class PartyService(context: ActorContext[Message], storage: ActorRef[DatabaseMes override def onMessage(msg: Message): Behavior[Message] = handle(Map.empty)(msg) private def handle(cache: Map[String, Party]): Message.Handler = { - case ForgetParty(partyId) => + case ControlMessage.ForgetParty(partyId) => Behaviors.receiveMessage(handle(cache - partyId)) - case GetNewPartyId(client) => + case ControlMessage.GetNewPartyId(client) => getPartyId.foreach(client ! _) Behaviors.same - case StoreParty(partyId, party) => + case ControlMessage.StoreParty(partyId, party) => Behaviors.receiveMessage(handle(cache.updated(partyId, party))) - case GetParty(partyId, client) => + case DatabaseMessage.GetParty(partyId, client) => val party = cache.get(partyId) match { case Some(party) => Future.successful(party) case None => - storage.ask(ref => GetParty(partyId, ref)).map { party => - context.self ! StoreParty(partyId, party) - context.system.scheduler.scheduleOnce(cacheTimeout, () => context.self ! ForgetParty(partyId)) + storage.ask(ref => DatabaseMessage.GetParty(partyId, ref)).map { party => + context.self ! ControlMessage.StoreParty(partyId, party) + context.system.scheduler + .scheduleOnce(cacheTimeout, () => context.self ! ControlMessage.ForgetParty(partyId)) party } } @@ -67,7 +68,7 @@ class PartyService(context: ActorContext[Message], storage: ActorRef[DatabaseMes private def getPartyId: Future[String] = { val partyId = Party.randomPartyId - storage.ask(ref => Exists(partyId, ref)).flatMap { + storage.ask(ref => DatabaseMessage.Exists(partyId, ref)).flatMap { case true => getPartyId case false => Future.successful(partyId) } diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala index d3e108e..d483026 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/BisProvider.scala @@ -13,7 +13,7 @@ import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} import akka.actor.typed.{Behavior, PostStop, Signal} import akka.http.scaladsl.model._ import com.typesafe.scalalogging.StrictLogging -import me.arcanis.ffxivbis.messages.{BiSProviderMessage, DownloadBiS} +import me.arcanis.ffxivbis.messages.BiSProviderMessage import me.arcanis.ffxivbis.models.{BiS, Job, Piece, PieceType} import me.arcanis.ffxivbis.service.bis.parser.Parser import me.arcanis.ffxivbis.service.bis.parser.impl.{Ariyala, Etro} @@ -21,6 +21,7 @@ import spray.json._ import java.nio.file.Paths import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NonFatal import scala.util.{Failure, Success} class BisProvider(context: ActorContext[BiSProviderMessage]) @@ -32,7 +33,7 @@ class BisProvider(context: ActorContext[BiSProviderMessage]) override def onMessage(msg: BiSProviderMessage): Behavior[BiSProviderMessage] = msg match { - case DownloadBiS(link, job, client) => + case BiSProviderMessage.DownloadBiS(link, job, client) => get(link, job).onComplete { case Success(items) => client ! BiS(items) case Failure(exception) => @@ -46,7 +47,7 @@ class BisProvider(context: ActorContext[BiSProviderMessage]) Behaviors.same } - private def get(link: String, job: Job.Job): Future[Seq[Piece]] = + private def get(link: String, job: Job): Future[Seq[Piece]] = try { val url = Uri(link) val id = Paths.get(link).normalize.getFileName.toString @@ -55,7 +56,7 @@ class BisProvider(context: ActorContext[BiSProviderMessage]) val uri = parser.uri(url, id) sendRequest(uri, BisProvider.parseBisJsonToPieces(job, parser, getPieceType)) } catch { - case exception: Exception => Future.failed(exception) + case NonFatal(exception) => Future.failed(exception) } } @@ -65,9 +66,9 @@ object BisProvider { Behaviors.setup[BiSProviderMessage](context => new BisProvider(context)) private def parseBisJsonToPieces( - job: Job.Job, + job: Job, idParser: Parser, - pieceTypes: Seq[Long] => Future[Map[Long, PieceType.PieceType]] + pieceTypes: Seq[Long] => Future[Map[Long, PieceType]] )(js: JsObject)(implicit executionContext: ExecutionContext): Future[Seq[Piece]] = idParser.parse(job, js).flatMap { pieces => pieceTypes(pieces.values.toSeq).map { types => diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala index 2db60cd..5046745 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/XivApi.scala @@ -22,7 +22,7 @@ trait XivApi extends RequestExecutor { private val xivapiUrl = config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-url") private val xivapiKey = Try(config.getString("me.arcanis.ffxivbis.bis-provider.xivapi-key")).toOption - private val preloadedItems: Map[Long, PieceType.PieceType] = + private val preloadedItems: Map[Long, PieceType] = config .getConfigList("me.arcanis.ffxivbis.bis-provider.cached-items") .asScala @@ -31,17 +31,16 @@ trait XivApi extends RequestExecutor { } .toMap - def getPieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType.PieceType]] = { - val (local, remote) = itemIds.foldLeft((Map.empty[Long, PieceType.PieceType], Seq.empty[Long])) { - case ((l, r), id) => - if (preloadedItems.contains(id)) (l.updated(id, preloadedItems(id)), r) - else (l, r :+ id) + def getPieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType]] = { + val (local, remote) = itemIds.foldLeft((Map.empty[Long, PieceType], Seq.empty[Long])) { case ((l, r), id) => + if (preloadedItems.contains(id)) (l.updated(id, preloadedItems(id)), r) + else (l, r :+ id) } if (remote.isEmpty) Future.successful(local) else remotePieceType(remote).map(_ ++ local) } - private def remotePieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType.PieceType]] = { + private def remotePieceType(itemIds: Seq[Long]): Future[Map[Long, PieceType]] = { val uriForItems = Uri(xivapiUrl) .withPath(Uri.Path / "item") .withQuery( @@ -111,7 +110,7 @@ object XivApi { private def parseXivapiJsonToType( shops: Map[Long, (String, Long)] - )(js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[Long, PieceType.PieceType]] = + )(js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[Long, PieceType]] = Future { val shopMap = js.fields("Results") match { case array: JsArray => diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala index fcf4481..694699e 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/Parser.scala @@ -17,7 +17,7 @@ import scala.concurrent.{ExecutionContext, Future} trait Parser extends StrictLogging { - def parse(job: Job.Job, js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[String, Long]] + def parse(job: Job, js: JsObject)(implicit executionContext: ExecutionContext): Future[Map[String, Long]] def uri(root: Uri, id: String): Uri } diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala index eefae33..8c4d1d8 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Ariyala.scala @@ -18,7 +18,7 @@ import scala.concurrent.{ExecutionContext, Future} object Ariyala extends Parser { - override def parse(job: Job.Job, js: JsObject)(implicit + override def parse(job: Job, js: JsObject)(implicit executionContext: ExecutionContext ): Future[Map[String, Long]] = Future { diff --git a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala index e4f357d..dbb75e4 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/bis/parser/impl/Etro.scala @@ -18,7 +18,7 @@ import scala.concurrent.{ExecutionContext, Future} object Etro extends Parser { - override def parse(job: Job.Job, js: JsObject)(implicit + override def parse(job: Job, js: JsObject)(implicit executionContext: ExecutionContext ): Future[Map[String, Long]] = Future { diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala index 6e4cd84..044913b 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseBiSHandler.scala @@ -10,7 +10,8 @@ package me.arcanis.ffxivbis.service.database.impl import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.service.database.Database trait DatabaseBiSHandler { this: Database => diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala index 6f9394a..4023e04 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseImpl.scala @@ -11,7 +11,8 @@ package me.arcanis.ffxivbis.service.database.impl import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext} import akka.actor.typed.{Behavior, DispatcherSelector} import com.typesafe.config.Config -import me.arcanis.ffxivbis.messages.{BisDatabaseMessage, DatabaseMessage, LootDatabaseMessage, PartyDatabaseMessage, UserDatabaseMessage} +import me.arcanis.ffxivbis.messages.DatabaseMessage +import me.arcanis.ffxivbis.messages.DatabaseMessage.{BisDatabaseMessage, LootDatabaseMessage, PartyDatabaseMessage, UserDatabaseMessage} import me.arcanis.ffxivbis.service.database.Database import me.arcanis.ffxivbis.storage.DatabaseProfile diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala index 53a36a2..b7a519a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseLootHandler.scala @@ -10,7 +10,8 @@ package me.arcanis.ffxivbis.service.database.impl import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models.Loot import me.arcanis.ffxivbis.service.database.Database diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala index 65a32c4..2e679bd 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabasePartyHandler.scala @@ -10,7 +10,8 @@ package me.arcanis.ffxivbis.service.database.impl import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models.{BiS, Player} import me.arcanis.ffxivbis.service.database.Database diff --git a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala index a4e52ae..5034252 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/database/impl/DatabaseUserHandler.scala @@ -10,7 +10,8 @@ package me.arcanis.ffxivbis.service.database.impl import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors -import me.arcanis.ffxivbis.messages._ +import me.arcanis.ffxivbis.messages.DatabaseMessage +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.service.database.Database trait DatabaseUserHandler { this: Database => diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala index bfc53c1..d3ade69 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/DatabaseProfile.scala @@ -16,6 +16,7 @@ import me.arcanis.ffxivbis.models.{Loot, Piece, PlayerId} import java.time.Instant import javax.sql.DataSource import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NonFatal class DatabaseProfile(override val executionContext: ExecutionContext, config: Config) extends StrictLogging @@ -31,7 +32,7 @@ class DatabaseProfile(override val executionContext: ExecutionContext, config: C val dataSourceConfig = DatabaseConnection.getDataSourceConfig(profile) new HikariDataSource(dataSourceConfig) } catch { - case exception: Exception => + case NonFatal(exception) => logger.error("exception during storage initialization", exception) throw exception } diff --git a/src/test/scala/me/arcanis/ffxivbis/ApplicationTest.scala b/src/test/scala/me/arcanis/ffxivbis/ApplicationTest.scala new file mode 100644 index 0000000..ad60e1d --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/ApplicationTest.scala @@ -0,0 +1,16 @@ +package me.arcanis.ffxivbis + +import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import org.scalatest.wordspec.AnyWordSpecLike + +class ApplicationTest extends ScalaTestWithActorTestKit(Settings.withRandomDatabase) + with AnyWordSpecLike { + + "application" must { + + "load" in { + testKit.spawn[Nothing](Application()) + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/ConfigurationTest.scala b/src/test/scala/me/arcanis/ffxivbis/ConfigurationTest.scala new file mode 100644 index 0000000..b782aa6 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/ConfigurationTest.scala @@ -0,0 +1,20 @@ +package me.arcanis.ffxivbis + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class ConfigurationTest extends AnyWordSpecLike with Matchers { + + private val requiredPaths = + Seq("akka.http.server.transparent-head-requests") + + "configuration helper" must { + + requiredPaths.foreach { path => + s"has $path propery" in { + Configuration.load().hasPath(path) shouldBe true + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala index 855bb84..7ae9042 100644 --- a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala +++ b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala @@ -6,49 +6,50 @@ import me.arcanis.ffxivbis.models._ import scala.concurrent.Future object Fixtures { + lazy val bis: BiS = BiS( Seq( - Weapon(pieceType = PieceType.Savage ,Job.DNC), - Head(pieceType = PieceType.Savage, Job.DNC), - Body(pieceType = PieceType.Savage, Job.DNC), - Hands(pieceType = PieceType.Tome, Job.DNC), - Legs(pieceType = PieceType.Tome, Job.DNC), - Feet(pieceType = PieceType.Savage, Job.DNC), - Ears(pieceType = PieceType.Savage, Job.DNC), - Neck(pieceType = PieceType.Tome, Job.DNC), - Wrist(pieceType = PieceType.Savage, Job.DNC), - Ring(pieceType = PieceType.Tome, Job.DNC, "left ring"), - Ring(pieceType = PieceType.Tome, Job.DNC, "right ring") + Piece.Weapon(pieceType = PieceType.Savage ,Job.DNC), + Piece.Head(pieceType = PieceType.Savage, Job.DNC), + Piece.Body(pieceType = PieceType.Savage, Job.DNC), + Piece.Hands(pieceType = PieceType.Tome, Job.DNC), + Piece.Legs(pieceType = PieceType.Tome, Job.DNC), + Piece.Feet(pieceType = PieceType.Savage, Job.DNC), + Piece.Ears(pieceType = PieceType.Savage, Job.DNC), + Piece.Neck(pieceType = PieceType.Tome, Job.DNC), + Piece.Wrist(pieceType = PieceType.Savage, Job.DNC), + Piece.Ring(pieceType = PieceType.Tome, Job.DNC, "left ring"), + Piece.Ring(pieceType = PieceType.Tome, Job.DNC, "right ring") ) ) lazy val bis2: BiS = BiS( Seq( - Weapon(pieceType = PieceType.Savage ,Job.DNC), - Head(pieceType = PieceType.Tome, Job.DNC), - Body(pieceType = PieceType.Savage, Job.DNC), - Hands(pieceType = PieceType.Tome, Job.DNC), - Legs(pieceType = PieceType.Savage, Job.DNC), - Feet(pieceType = PieceType.Tome, Job.DNC), - Ears(pieceType = PieceType.Savage, Job.DNC), - Neck(pieceType = PieceType.Savage, Job.DNC), - Wrist(pieceType = PieceType.Savage, Job.DNC), - Ring(pieceType = PieceType.Tome, Job.DNC, "left ring"), - Ring(pieceType = PieceType.Savage, Job.DNC, "right ring") + Piece.Weapon(pieceType = PieceType.Savage ,Job.DNC), + Piece.Head(pieceType = PieceType.Tome, Job.DNC), + Piece.Body(pieceType = PieceType.Savage, Job.DNC), + Piece.Hands(pieceType = PieceType.Tome, Job.DNC), + Piece.Legs(pieceType = PieceType.Savage, Job.DNC), + Piece.Feet(pieceType = PieceType.Tome, Job.DNC), + Piece.Ears(pieceType = PieceType.Savage, Job.DNC), + Piece.Neck(pieceType = PieceType.Savage, Job.DNC), + Piece.Wrist(pieceType = PieceType.Savage, Job.DNC), + Piece.Ring(pieceType = PieceType.Tome, Job.DNC, "left ring"), + Piece.Ring(pieceType = PieceType.Savage, Job.DNC, "right ring") ) ) lazy val bis3: BiS = BiS( Seq( - Weapon(pieceType = PieceType.Savage ,Job.SGE), - Head(pieceType = PieceType.Tome, Job.SGE), - Body(pieceType = PieceType.Savage, Job.SGE), - Hands(pieceType = PieceType.Tome, Job.SGE), - Legs(pieceType = PieceType.Tome, Job.SGE), - Feet(pieceType = PieceType.Savage, Job.SGE), - Ears(pieceType = PieceType.Savage, Job.SGE), - Neck(pieceType = PieceType.Tome, Job.SGE), - Wrist(pieceType = PieceType.Savage, Job.SGE), - Ring(pieceType = PieceType.Savage, Job.SGE, "left ring"), - Ring(pieceType = PieceType.Tome, Job.SGE, "right ring") + Piece.Weapon(pieceType = PieceType.Savage ,Job.SGE), + Piece.Head(pieceType = PieceType.Tome, Job.SGE), + Piece.Body(pieceType = PieceType.Savage, Job.SGE), + Piece.Hands(pieceType = PieceType.Tome, Job.SGE), + Piece.Legs(pieceType = PieceType.Tome, Job.SGE), + Piece.Feet(pieceType = PieceType.Savage, Job.SGE), + Piece.Ears(pieceType = PieceType.Savage, Job.SGE), + Piece.Neck(pieceType = PieceType.Tome, Job.SGE), + Piece.Wrist(pieceType = PieceType.Savage, Job.SGE), + Piece.Ring(pieceType = PieceType.Savage, Job.SGE, "left ring"), + Piece.Ring(pieceType = PieceType.Tome, Job.SGE, "right ring") ) ) @@ -58,16 +59,16 @@ object Fixtures { lazy val link4: String = "https://etro.gg/gearset/865fc886-994f-4c28-8fc1-4379f160a916" lazy val link5: String = "https://ffxiv.ariyala.com/1FGU0" - lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob) - lazy val lootBody: Piece = Body(pieceType = PieceType.Savage, Job.AnyJob) - lazy val lootBodyCrafted: Piece = Body(pieceType = PieceType.Crafted, Job.AnyJob) - lazy val lootHands: Piece = Hands(pieceType = PieceType.Tome, Job.AnyJob) - lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob) - lazy val lootEars: Piece = Ears(pieceType = PieceType.Savage, Job.AnyJob) - lazy val lootRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob) - lazy val lootLeftRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob, "left ring") - lazy val lootRightRing: Piece = Ring(pieceType = PieceType.Tome, Job.AnyJob, "right ring") - lazy val lootUpgrade: Piece = BodyUpgrade + lazy val lootWeapon: Piece = Piece.Weapon(pieceType = PieceType.Tome, Job.AnyJob) + lazy val lootBody: Piece = Piece.Body(pieceType = PieceType.Savage, Job.AnyJob) + lazy val lootBodyCrafted: Piece = Piece.Body(pieceType = PieceType.Crafted, Job.AnyJob) + lazy val lootHands: Piece = Piece.Hands(pieceType = PieceType.Tome, Job.AnyJob) + lazy val lootLegs: Piece = Piece.Legs(pieceType = PieceType.Savage, Job.AnyJob) + lazy val lootEars: Piece = Piece.Ears(pieceType = PieceType.Savage, Job.AnyJob) + lazy val lootRing: Piece = Piece.Ring(pieceType = PieceType.Tome, Job.AnyJob) + lazy val lootLeftRing: Piece = Piece.Ring(pieceType = PieceType.Tome, Job.AnyJob, "left ring") + lazy val lootRightRing: Piece = Piece.Ring(pieceType = PieceType.Tome, Job.AnyJob, "right ring") + lazy val lootUpgrade: Piece = Piece.BodyUpgrade lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade) lazy val partyId: String = Party.randomPartyId @@ -84,5 +85,5 @@ object Fixtures { lazy val users: Seq[User] = Seq(userAdmin, userGet) lazy val authProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(Some(userAdmin)) - lazy val rejectingProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(None) + lazy val rejectingAuthProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(None) } diff --git a/src/test/scala/me/arcanis/ffxivbis/Settings.scala b/src/test/scala/me/arcanis/ffxivbis/Settings.scala index 28c657f..5828294 100644 --- a/src/test/scala/me/arcanis/ffxivbis/Settings.scala +++ b/src/test/scala/me/arcanis/ffxivbis/Settings.scala @@ -5,11 +5,12 @@ import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory} import java.io.File object Settings { + def config(values: Map[String, AnyRef]): Config = { @scala.annotation.tailrec - def replace(acc: Config, iter: List[(String, AnyRef)]): Config = iter match { - case Nil => acc - case (key -> value) :: tail => replace(acc.withValue(key, ConfigValueFactory.fromAnyRef(value)), tail) + def replace(config: Config, iter: List[(String, AnyRef)]): Config = iter match { + case Nil => config + case (key -> value) :: tail => replace(config.withValue(key, ConfigValueFactory.fromAnyRef(value)), tail) } val default = ConfigFactory.load() @@ -23,7 +24,9 @@ object Settings { if (databaseFile.exists) databaseFile.delete() } + def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString + def withRandomDatabase: Config = config(Map("me.arcanis.ffxivbis.database.sqlite.jdbcUrl" -> s"jdbc:sqlite:$randomDatabasePath")) } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala index ca424a8..955afc4 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala @@ -1,7 +1,7 @@ package me.arcanis.ffxivbis.http -import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} +import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest import me.arcanis.ffxivbis.Fixtures @@ -25,7 +25,7 @@ class AuthorizationTest extends AnyWordSpecLike with Matchers with ScalatestRout } "reject credentials" in { - val route = new RootView(Fixtures.rejectingProvider).routes + val route = new RootView(Fixtures.rejectingAuthProvider).routes Get(Uri(s"/party/${Fixtures.partyId}")).withHeaders(auth) ~> Route.seal(route) ~> check { status shouldEqual StatusCodes.Unauthorized diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala index 2a88aa2..b259c41 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpointTest.scala @@ -8,7 +8,7 @@ import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestKit import com.typesafe.config.Config import me.arcanis.ffxivbis.http.api.v1.json._ -import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser} +import me.arcanis.ffxivbis.messages.DatabaseMessage.{AddPlayer, AddUser} import me.arcanis.ffxivbis.models.{BiS, Job} import me.arcanis.ffxivbis.service.PartyService import me.arcanis.ffxivbis.service.bis.BisProvider @@ -53,15 +53,6 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT super.afterAll() } - private def compareBiSResponse(actual: PlayerModel, expected: PlayerModel): Unit = { - actual.partyId shouldEqual expected.partyId - actual.nick shouldEqual expected.nick - actual.job shouldEqual expected.job - Compare.seqEquals(actual.bis.get, expected.bis.get) shouldEqual true - actual.link shouldEqual expected.link - actual.priority shouldEqual expected.priority - } - "api v1 bis endpoint" must { "create best in slot set from ariyala" in { @@ -216,4 +207,13 @@ class BiSEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteT } } + + private def compareBiSResponse(actual: PlayerModel, expected: PlayerModel): Unit = { + actual.partyId shouldEqual expected.partyId + actual.nick shouldEqual expected.nick + actual.job shouldEqual expected.job + Compare.seqEquals(actual.bis.get, expected.bis.get) shouldEqual true + actual.link shouldEqual expected.link + actual.priority shouldEqual expected.priority + } } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandlerTest.scala new file mode 100644 index 0000000..5e80f9a --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandlerTest.scala @@ -0,0 +1,78 @@ +package me.arcanis.ffxivbis.http.api.v1 + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.model.headers.Allow +import akka.http.scaladsl.server.Directives.{path, _} +import akka.http.scaladsl.server.Route +import akka.http.scaladsl.testkit.ScalatestRouteTest +import me.arcanis.ffxivbis.http.api.v1.json._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class HttpHandlerTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport with HttpHandler { + + "http handler" must { + + "convert IllegalArgumentException into 400 response" in { + Get("/400") ~> withExceptionHandler("400", failWith(new IllegalArgumentException(""))) ~> check { + status shouldEqual StatusCodes.BadRequest + responseAs[ErrorModel] shouldEqual ErrorModel("") + } + } + + "convert IllegalArgumentException into 400 response with details" in { + Get("/400") ~> withExceptionHandler("400", failWith(new IllegalArgumentException("message"))) ~> check { + status shouldEqual StatusCodes.BadRequest + responseAs[ErrorModel] shouldEqual ErrorModel("message") + } + } + + "convert exception message to error response" in { + Get("/500") ~> withExceptionHandler("500", failWith(new ArithmeticException)) ~> check { + status shouldEqual StatusCodes.InternalServerError + responseAs[ErrorModel] shouldEqual ErrorModel("unknown server error") + } + } + + "process OPTIONS request" in { + Options("/200") ~> withRejectionHandler() ~> check { + status shouldEqual StatusCodes.OK + headers.collectFirst { case header: Allow => header } should not be empty + responseAs[String] shouldBe empty + } + } + + "reject unknown request" in { + Post("/200") ~> withRejectionHandler() ~> check { + status shouldEqual StatusCodes.MethodNotAllowed + headers.collectFirst { case header: Allow => header } should not be empty + responseAs[ErrorModel].message should not be empty + } + } + + "handle 404 response" in { + Get("/404") ~> withRejectionHandler() ~> check { + status shouldEqual StatusCodes.NotFound + responseAs[ErrorModel] shouldEqual ErrorModel("The requested resource could not be found.") + } + } + + } + + private def single(uri: String, completeWith: Route) = + path(uri)(get(completeWith)) + + private def withExceptionHandler(uri: String = "200", completeWith: Route = complete(StatusCodes.OK)) = + Route.seal { + handleExceptions(exceptionHandler) { + single(uri, completeWith) + } + } + + private def withRejectionHandler(uri: String = "200", completeWith: Route = complete(StatusCodes.OK)) = + Route.seal { + handleRejections(rejectionHandler) { + single(uri, completeWith) + } + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala index 8957e68..56b79c5 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala @@ -8,7 +8,7 @@ import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestKit import com.typesafe.config.Config import me.arcanis.ffxivbis.http.api.v1.json._ -import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser} +import me.arcanis.ffxivbis.messages.DatabaseMessage.{AddPlayer, AddUser} import me.arcanis.ffxivbis.service.PartyService import me.arcanis.ffxivbis.service.database.{Database, Migration} import me.arcanis.ffxivbis.{Fixtures, Settings} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala index be68fd0..6e3e30d 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PartyEndpointTest.scala @@ -8,7 +8,7 @@ import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestKit import com.typesafe.config.Config import me.arcanis.ffxivbis.http.api.v1.json._ -import me.arcanis.ffxivbis.messages.AddUser +import me.arcanis.ffxivbis.messages.DatabaseMessage.AddUser import me.arcanis.ffxivbis.models.PartyDescription import me.arcanis.ffxivbis.service.PartyService import me.arcanis.ffxivbis.service.bis.BisProvider diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala index 4d47504..e35765c 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala @@ -7,9 +7,8 @@ import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestKit import com.typesafe.config.Config -import me.arcanis.ffxivbis import me.arcanis.ffxivbis.http.api.v1.json._ -import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser} +import me.arcanis.ffxivbis.messages.DatabaseMessage.{AddPlayer, AddUser} import me.arcanis.ffxivbis.service.PartyService import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.database.{Database, Migration} diff --git a/src/test/scala/me/arcanis/ffxivbis/models/BiSTest.scala b/src/test/scala/me/arcanis/ffxivbis/models/BiSTest.scala index 3454010..16830d0 100644 --- a/src/test/scala/me/arcanis/ffxivbis/models/BiSTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/models/BiSTest.scala @@ -46,7 +46,7 @@ class BiSTest extends AnyWordSpecLike with Matchers { } "return upgrade list" in { - Compare.mapEquals(Fixtures.bis.upgrades, Map[PieceUpgrade, Int](BodyUpgrade -> 2, AccessoryUpgrade -> 3)) shouldEqual true + Compare.mapEquals(Fixtures.bis.upgrades, Map[Piece.PieceUpgrade, Int](Piece.BodyUpgrade -> 2, Piece.AccessoryUpgrade -> 3)) shouldEqual true } } diff --git a/src/test/scala/me/arcanis/ffxivbis/models/PieceTest.scala b/src/test/scala/me/arcanis/ffxivbis/models/PieceTest.scala index d847bd9..cab1b73 100644 --- a/src/test/scala/me/arcanis/ffxivbis/models/PieceTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/models/PieceTest.scala @@ -9,13 +9,13 @@ class PieceTest extends AnyWordSpecLike with Matchers { "piece model" must { "return upgrade" in { - Fixtures.lootWeapon.upgrade shouldEqual Some(WeaponUpgrade) + Fixtures.lootWeapon.upgrade shouldEqual Some(Piece.WeaponUpgrade) Fixtures.lootBody.upgrade shouldEqual None - Fixtures.lootHands.upgrade shouldEqual Some(BodyUpgrade) + Fixtures.lootHands.upgrade shouldEqual Some(Piece.BodyUpgrade) Fixtures.lootLegs.upgrade shouldEqual None Fixtures.lootEars.upgrade shouldEqual None - Fixtures.lootLeftRing.upgrade shouldEqual Some(AccessoryUpgrade) - Fixtures.lootRightRing.upgrade shouldEqual Some(AccessoryUpgrade) + Fixtures.lootLeftRing.upgrade shouldEqual Some(Piece.AccessoryUpgrade) + Fixtures.lootRightRing.upgrade shouldEqual Some(Piece.AccessoryUpgrade) } "build piece from string" in { diff --git a/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala index fdeca76..888bbfb 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/LootSelectorTest.scala @@ -2,7 +2,7 @@ package me.arcanis.ffxivbis.service import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.typed.scaladsl.AskPattern.Askable -import me.arcanis.ffxivbis.messages.DownloadBiS +import me.arcanis.ffxivbis.messages.BiSProviderMessage.DownloadBiS import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.{Fixtures, Settings} @@ -28,10 +28,10 @@ class LootSelectorTest extends AnyWordSpecLike with Matchers with BeforeAndAfter val testKit = ActorTestKit(Settings.withRandomDatabase) val provider = testKit.spawn(BisProvider()) - val dncSet = Await.result(provider.ask(DownloadBiS(Fixtures.link, Job.DNC, _) )(timeout, testKit.scheduler), timeout) + val dncSet = Await.result(provider.ask(DownloadBiS(Fixtures.link, Job.DNC, _))(timeout, testKit.scheduler), timeout) dnc = dnc.withBiS(Some(dncSet)) - val drgSet = Await.result(provider.ask(DownloadBiS(Fixtures.link2, Job.DRG, _) )(timeout, testKit.scheduler), timeout) + val drgSet = Await.result(provider.ask(DownloadBiS(Fixtures.link2, Job.DRG, _))(timeout, testKit.scheduler), timeout) drg = drg.withBiS(Some(drgSet)) default = default.withPlayer(dnc).withPlayer(drg) @@ -43,11 +43,11 @@ class LootSelectorTest extends AnyWordSpecLike with Matchers with BeforeAndAfter "loot selector" must { "suggest loot by isRequired" in { - toPlayerId(default.suggestLoot(Head(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(dnc.playerId, drg.playerId) + toPlayerId(default.suggestLoot(Piece.Head(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(dnc.playerId, drg.playerId) } "suggest loot if a player already have it" in { - val piece = Body(pieceType = PieceType.Savage, Job.AnyJob) + val piece = Piece.Body(pieceType = PieceType.Savage, Job.AnyJob) val party = default.withPlayer(dnc.withLoot(piece)) toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId) @@ -56,34 +56,34 @@ class LootSelectorTest extends AnyWordSpecLike with Matchers with BeforeAndAfter "suggest upgrade" in { val party = default.withPlayer( dnc.withBiS( - Some(dnc.bis.withPiece(Weapon(pieceType = PieceType.Tome, Job.DNC))) + Some(dnc.bis.withPiece(Piece.Weapon(pieceType = PieceType.Tome, Job.DNC))) ) ) - toPlayerId(party.suggestLoot(WeaponUpgrade)) shouldEqual Seq(dnc.playerId, drg.playerId) + toPlayerId(party.suggestLoot(Piece.WeaponUpgrade)) shouldEqual Seq(dnc.playerId, drg.playerId) } "suggest loot by priority" in { val party = default.withPlayer(dnc.copy(priority = 2)) - toPlayerId(party.suggestLoot(Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId) + toPlayerId(party.suggestLoot(Piece.Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId) } "suggest loot by bis pieces got" in { - val party = default.withPlayer(dnc.withLoot(Head(pieceType = PieceType.Savage, Job.AnyJob))) + val party = default.withPlayer(dnc.withLoot(Piece.Head(pieceType = PieceType.Savage, Job.AnyJob))) - toPlayerId(party.suggestLoot(Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId) + toPlayerId(party.suggestLoot(Piece.Body(pieceType = PieceType.Savage, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId) } "suggest loot by this piece got" in { - val piece = Body(pieceType = PieceType.Tome, Job.AnyJob) + val piece = Piece.Body(pieceType = PieceType.Tome, Job.AnyJob) val party = default.withPlayer(dnc.withLoot(piece)) toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId) } "suggest loot by total piece got" in { - val piece = Body(pieceType = PieceType.Tome, Job.AnyJob) + val piece = Piece.Body(pieceType = PieceType.Tome, Job.AnyJob) val party = default .withPlayer(dnc.withLoot(Seq(piece, piece).map(pieceToLoot))) .withPlayer(drg.withLoot(piece)) diff --git a/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala index 6e25354..987cb5c 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/bis/BisProviderTest.scala @@ -1,7 +1,7 @@ package me.arcanis.ffxivbis.service.bis import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import me.arcanis.ffxivbis.messages.DownloadBiS +import me.arcanis.ffxivbis.messages.BiSProviderMessage.DownloadBiS import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.{Fixtures, Settings} import org.scalatest.wordspec.AnyWordSpecLike diff --git a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseBiSHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseBiSHandlerTest.scala index 1bcb616..ade9cd5 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseBiSHandlerTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseBiSHandlerTest.scala @@ -2,7 +2,7 @@ package me.arcanis.ffxivbis.service.database import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.typed.scaladsl.AskPattern.Askable -import me.arcanis.ffxivbis.messages.{AddPieceToBis, AddPlayer, GetBiS, RemovePieceFromBiS} +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.{Fixtures, Settings} @@ -70,7 +70,7 @@ class DatabaseBiSHandlerTest extends ScalaTestWithActorTestKit(Settings.withRand "update piece in bis set" in { val updateProbe = testKit.createTestProbe[Unit]() - val newPiece = Hands(pieceType = PieceType.Savage, Job.DNC) + val newPiece = Piece.Hands(pieceType = PieceType.Savage, Job.DNC) database ! AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece, updateProbe.ref) updateProbe.expectMessage(askTimeout, ()) diff --git a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseLootHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseLootHandlerTest.scala index bc3e1d4..56c273a 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseLootHandlerTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseLootHandlerTest.scala @@ -2,8 +2,7 @@ package me.arcanis.ffxivbis.service.database import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.typed.scaladsl.AskPattern.Askable -import ch.qos.logback.core.util.FixedDelay -import me.arcanis.ffxivbis.messages.{AddPieceTo, AddPlayer, GetLoot, RemovePieceFrom} +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.{Fixtures, Settings} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala index 1cbc548..aac8d1f 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabasePartyHandlerTest.scala @@ -1,7 +1,7 @@ package me.arcanis.ffxivbis.service.database import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import me.arcanis.ffxivbis.messages.{AddPlayer, GetParty, GetPlayer, RemovePlayer} +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.{Fixtures, Settings} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseUserHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseUserHandlerTest.scala index 7ed77dc..6a9db58 100644 --- a/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseUserHandlerTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/service/database/DatabaseUserHandlerTest.scala @@ -1,7 +1,7 @@ package me.arcanis.ffxivbis.service.database import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import me.arcanis.ffxivbis.messages.{AddUser, DeleteUser, GetUser, GetUsers} +import me.arcanis.ffxivbis.messages.DatabaseMessage._ import me.arcanis.ffxivbis.models.User import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.{Fixtures, Settings} @@ -80,5 +80,6 @@ class DatabaseUserHandlerTest extends ScalaTestWithActorTestKit(Settings.withRan database ! GetUser(Fixtures.partyId, Fixtures.userGet.username, probe.ref) probe.expectMessage(askTimeout, None) } + } } diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala index 2fb0cea..c11b9d7 100644 --- a/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala +++ b/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala @@ -1,6 +1,7 @@ package me.arcanis.ffxivbis.utils object Compare { + def mapEquals[K, T](left: Map[K, T], right: Map[K, T]): Boolean = left.forall { case (key, value) => right.contains(key) && right(key) == value diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala index 650e540..7df1cf6 100644 --- a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala +++ b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala @@ -8,5 +8,4 @@ import scala.language.implicitConversions object Converters { implicit def pieceToLoot(piece: Piece): Loot = Loot(-1, piece, Instant.ofEpochMilli(0), isFreeLoot = false) - } diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/ImplicitsTest.scala b/src/test/scala/me/arcanis/ffxivbis/utils/ImplicitsTest.scala new file mode 100644 index 0000000..45f0607 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/utils/ImplicitsTest.scala @@ -0,0 +1,43 @@ +package me.arcanis.ffxivbis.utils + +import akka.util.Timeout +import me.arcanis.ffxivbis.Settings +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.FiniteDuration + +class ImplicitsTest extends AnyWordSpecLike with Matchers { + + import me.arcanis.ffxivbis.utils.Implicits._ + + "configuration extension" must { + + "return finite duration" in { + val config = Settings.config(Map("value" -> "1m")) + config.getFiniteDuration("value") shouldBe FiniteDuration(1, TimeUnit.MINUTES) + } + + "return optional string" in { + val config = Settings.config(Map("value" -> "string")) + config.getOptString("value") shouldBe Some("string") + } + + "return None for missing optional string" in { + val config = Settings.config(Map.empty) + config.getOptString("value") shouldBe None + } + + "return None for empty optional string" in { + val config = Settings.config(Map("value" -> "")) + config.getOptString("value") shouldBe None + } + + "return timeout" in { + val config = Settings.config(Map("value" -> "1m")) + config.getTimeout("value") shouldBe Timeout(1, TimeUnit.MINUTES) + } + + } +}