more tests

This commit is contained in:
Evgenii Alekseev 2019-10-26 19:44:51 +03:00
parent b228595a1b
commit 4cdcd80d51
27 changed files with 345 additions and 87 deletions

View File

@ -11,8 +11,7 @@ package me.arcanis.ffxivbis.http
import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
import me.arcanis.ffxivbis.models.{Player, PlayerId}
import me.arcanis.ffxivbis.service.Party
import me.arcanis.ffxivbis.models.{Party, Player, PlayerId}
import me.arcanis.ffxivbis.service.impl.{DatabaseBiSHandler, DatabasePartyHandler}
import scala.concurrent.{ExecutionContext, Future}

View File

@ -15,7 +15,7 @@ case class PieceResponse(
@Schema(description = "is piece tome gear", required = true) isTome: Boolean,
@Schema(description = "job name to which piece belong or AnyJob", required = true, example = "DNC") job: String,
@Schema(description = "piece name", required = true, example = "body") piece: String) {
def toPiece: Piece = Piece(piece, isTome, Job.fromString(job))
def toPiece: Piece = Piece(piece, isTome, Job.withName(job))
}
object PieceResponse {

View File

@ -16,5 +16,5 @@ case class PlayerIdResponse(
@Schema(description = "job name", required = true, example = "DNC") job: String,
@Schema(description = "player nick name", required = true, example = "Siuan Sanche") nick: String) {
def withPartyId(partyId: String): PlayerId =
PlayerId(partyId, Job.fromString(job), nick)
PlayerId(partyId, Job.withName(job), nick)
}

View File

@ -20,7 +20,7 @@ case class PlayerResponse(
@Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String],
@Schema(description = "player loot priority") priority: Option[Int]) {
def toPlayer: Player =
Player(partyId, Job.fromString(job), nick,
Player(partyId, Job.withName(job), nick,
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), loot.getOrElse(Seq.empty).map(_.toPiece),
link, priority.getOrElse(0))
}

View File

@ -14,8 +14,7 @@ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._
import akka.util.Timeout
import me.arcanis.ffxivbis.http.UserHelper
import me.arcanis.ffxivbis.models.{Permission, User}
import me.arcanis.ffxivbis.service.Party
import me.arcanis.ffxivbis.models.{Party, Permission, User}
class IndexView(storage: ActorRef)(implicit timeout: Timeout)
extends UserHelper(storage) {

View File

@ -92,7 +92,7 @@ object PlayerView {
form(action:=s"/party/$partyId/players", method:="post")(
input(name:="nick", id:="nick", placeholder:="nick", title:="nick", `type`:="nick"),
select(name:="job", id:="job", title:="job")
(for (job <- Job.groupAll) yield option(job.toString)),
(for (job <- Job.jobs) yield option(job.toString)),
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
input(name:="prioiry", id:="priority", placeholder:="priority", title:="priority", `type`:="number", value:="0"),
input(name:="action", id:="action", `type`:="hidden", value:="add"),

View File

@ -76,5 +76,5 @@ object BiS {
def apply(): BiS = BiS(Seq.empty)
def apply(pieces: Seq[Piece]): BiS =
BiS(pieces.map { piece => piece.piece -> Some(piece) }.toMap)
BiS(pieces.map(piece => piece.piece -> Some(piece)).toMap)
}

View File

@ -9,65 +9,95 @@
package me.arcanis.ffxivbis.models
object Job {
sealed trait Job
sealed trait RightSide
object AccessoriesDex extends RightSide
object AccessoriesInt extends RightSide
object AccessoriesMnd extends RightSide
object AccessoriesStr extends RightSide
object AccessoriesVit extends RightSide
sealed trait LeftSide
object BodyCasters extends LeftSide
object BodyDrgs extends LeftSide
object BodyHealers extends LeftSide
object BodyMnks extends LeftSide
object BodyNins extends LeftSide
object BodyTanks extends LeftSide
object BodyRanges extends LeftSide
sealed trait Job {
def leftSide: LeftSide
def rightSide: RightSide
// conversion to string to avoid recursion
override def equals(obj: Any): Boolean = {
def canEqual(obj: Any): Boolean = obj.isInstanceOf[Job]
def equality(objRepr: String): Boolean = objRepr match {
case _ if objRepr == AnyJob.toString => true
case _ if this.toString == AnyJob.toString => true
case _ => this.toString == obj.toString
}
canEqual(obj) && equality(obj.toString)
}
}
case object AnyJob extends Job {
override def equals(obj: Any): Boolean = obj match {
case Job => true
case _ => false
}
val leftSide: LeftSide = null
val rightSide: RightSide = null
}
case object PLD extends Job
case object WAR extends Job
case object DRK extends Job
case object GNB extends Job
case object WHM extends Job
case object SCH extends Job
case object AST extends Job
case object MNK extends Job
case object DRG extends Job
case object NIN extends Job
case object SAM extends Job
case object BRD extends Job
case object MCH extends Job
case object DNC extends Job
case object BLM extends Job
case object SMN extends Job
case object RDM extends Job
def groupAccessoriesDex: Seq[Job.Job] = groupRanges :+ NIN
def groupAccessoriesStr: Seq[Job.Job] = groupMnk :+ DRG
def groupAll: Seq[Job.Job] = groupCasters ++ groupHealers ++ groupRanges ++ groupTanks
def groupCasters: Seq[Job.Job] = Seq(BLM, SMN, RDM)
def groupHealers: Seq[Job.Job] = Seq(WHM, SCH, AST)
def groupMnk: Seq[Job.Job] = Seq(MNK, SAM)
def groupRanges: Seq[Job.Job] = Seq(BRD, MCH, DNC)
def groupTanks: Seq[Job.Job] = Seq(PLD, WAR, DRK, GNB)
def groupFull: Seq[Seq[Job.Job]] = Seq(groupCasters, groupHealers, groupMnk, groupRanges, groupTanks)
def groupRight: Seq[Seq[Job.Job]] = Seq(groupAccessoriesDex, groupAccessoriesStr)
def fromString(job: String): Job.Job = groupAll.find(_.toString == job.toUpperCase).orNull
def hasSameLoot(left: Job, right: Job, piece: Piece): Boolean = {
def isAccessory(piece: Piece): Boolean = piece match {
case _: PieceAccessory => true
case _ => false
}
def isWeapon(piece: Piece): Boolean = piece match {
case _: PieceWeapon => true
case _ => false
}
if (left == right) true
else if (isWeapon(piece)) false
else if (groupFull.exists(group => group.contains(left) && group.contains(right))) true
else if (isAccessory(piece) && groupRight.exists(group => group.contains(left) && group.contains(right))) true
else false
trait Casters extends Job {
val leftSide: LeftSide = BodyCasters
val rightSide: RightSide = AccessoriesInt
}
trait Healers extends Job {
val leftSide: LeftSide = BodyHealers
val rightSide: RightSide = AccessoriesMnd
}
trait Mnks extends Job {
val leftSide: LeftSide = BodyMnks
val rightSide: RightSide = AccessoriesStr
}
trait Tanks extends Job {
val leftSide: LeftSide = BodyTanks
val rightSide: RightSide = AccessoriesVit
}
trait Ranges extends Job {
val leftSide: LeftSide = BodyRanges
val rightSide: RightSide = AccessoriesDex
}
case object PLD extends Tanks
case object WAR extends Tanks
case object DRK extends Tanks
case object GNB extends Tanks
case object WHM extends Healers
case object SCH extends Healers
case object AST extends Healers
case object MNK extends Mnks
case object DRG extends Job {
val leftSide: LeftSide = BodyDrgs
val rightSide: RightSide = AccessoriesStr
}
case object NIN extends Job {
val leftSide: LeftSide = BodyNins
val rightSide: RightSide = AccessoriesDex
}
case object SAM extends Mnks
case object BRD extends Ranges
case object MCH extends Ranges
case object DNC extends Ranges
case object BLM extends Casters
case object SMN extends Casters
case object RDM extends Casters
lazy val jobs: Seq[Job] =
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM)
def withName(job: String): Job.Job = jobs.find(_.toString == job.toUpperCase).getOrElse(AnyJob)
}

View File

@ -6,20 +6,18 @@
*
* License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
*/
package me.arcanis.ffxivbis.service
package me.arcanis.ffxivbis.models
import com.typesafe.config.Config
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.models.{BiS, Loot, Piece, Player, PlayerId}
import me.arcanis.ffxivbis.service.LootSelector
import scala.jdk.CollectionConverters._
import scala.util.Random
case class Party(partyId: String, config: Config, players: Map[PlayerId, Player])
case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Player])
extends StrictLogging {
private val rules =
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
require(players.keys.forall(_.partyId == partyId), "party id must be same")
def getPlayers: Seq[Player] = players.values.toSeq
def player(playerId: PlayerId): Option[Player] = players.get(playerId)
@ -38,8 +36,11 @@ case class Party(partyId: String, config: Config, players: Map[PlayerId, Player]
}
object Party {
private def getRules(config: Config): Seq[String] =
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
def apply(partyId: Option[String], config: Config): Party =
new Party(partyId.getOrElse(randomPartyId), config, Map.empty)
new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty)
def apply(partyId: String, config: Config,
players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = {
@ -51,7 +52,7 @@ object Party {
.withBiS(bisByPlayer.get(playerId))
.withLoot(lootByPlayer.get(playerId)))
}
Party(partyId, config, playersWithItems)
Party(partyId, getRules(config), playersWithItems)
}
def randomPartyId: String = Random.alphanumeric.take(20).mkString

View File

@ -104,7 +104,7 @@ object Piece {
case other => throw new Error(s"Unknown item type $other")
}
def available: Seq[String] = Seq("weapon",
lazy val available: Seq[String] = Seq("weapon",
"head", "body", "hands", "waist", "legs", "feet",
"ears", "neck", "wrist", "leftRing", "rightRing")
}

View File

@ -15,7 +15,7 @@ case class Player(partyId: String,
loot: Seq[Piece],
link: Option[String] = None,
priority: Int = 0) {
require(job != Job.AnyJob, "AnyJob is not allowed")
require(job ne Job.AnyJob, "AnyJob is not allowed")
val playerId: PlayerId = PlayerId(partyId, job, nick)
def withBiS(set: Option[BiS]): Player = set match {

View File

@ -23,13 +23,13 @@ case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerI
object PlayerId {
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
(maybeNick, maybeJob) match {
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.fromString(job), nick)).toOption
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption
case _ => None
}
private val prettyPlayerIdRegex: Regex = "^(.*) \\(([A-Z]{3})\\)$".r
def apply(partyId: String, player: String): Option[PlayerId] = player match {
case s"${prettyPlayerIdRegex(nick, job)}" => Try(PlayerId(partyId, Job.fromString(job), nick)).toOption
case s"${prettyPlayerIdRegex(nick, job)}" => Try(PlayerId(partyId, Job.withName(job), nick)).toOption
case _ => None
}
}

View File

@ -10,7 +10,7 @@ package me.arcanis.ffxivbis.service
import akka.actor.Actor
import com.typesafe.scalalogging.StrictLogging
import me.arcanis.ffxivbis.models.{Player, PlayerId}
import me.arcanis.ffxivbis.models.{Party, Player, PlayerId}
import me.arcanis.ffxivbis.storage.DatabaseProfile
import scala.concurrent.{ExecutionContext, Future}

View File

@ -17,7 +17,7 @@ trait BiSProfile { this: DatabaseProfile =>
import dbConfig.profile.api._
case class BiSRep(playerId: Long, created: Long, piece: String, isTome: Int, job: String) {
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.fromString(job)))
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.withName(job)))
}
object BiSRep {
def fromPiece(playerId: Long, piece: Piece) =

View File

@ -18,7 +18,7 @@ trait LootProfile { this: DatabaseProfile =>
case class LootRep(lootId: Option[Long], playerId: Long, created: Long, piece: String,
isTome: Int, job: String) {
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.fromString(job)))
def toLoot: Loot = Loot(playerId, Piece(piece, isTome == 1, Job.withName(job)))
}
object LootRep {
def fromPiece(playerId: Long, piece: Piece) =

View File

@ -19,7 +19,7 @@ trait PlayersProfile { this: DatabaseProfile =>
case class PlayerRep(partyId: String, playerId: Option[Long], created: Long, nick: String,
job: String, link: Option[String], priority: Int) {
def toPlayer: Player =
Player(partyId, Job.fromString(job), nick, BiS(Seq.empty), List.empty, link, priority)
Player(partyId, Job.withName(job), nick, BiS(Seq.empty), List.empty, link, priority)
}
object PlayerRep {
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =

View File

@ -0,0 +1,55 @@
package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class BiSTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"bis model" must {
"build set from list" in {
BiS(Fixtures.bis.pieces) shouldEqual Fixtures.bis
}
"has piece" in {
Fixtures.bis.hasPiece(Fixtures.lootBody) shouldEqual true
Fixtures.bis.hasPiece(Fixtures.lootHands) shouldEqual true
}
"has upgrade" in {
Fixtures.bis.hasPiece(Fixtures.lootUpgrade) shouldEqual true
}
"does not have piece" in {
Fixtures.bis.hasPiece(Fixtures.lootLegs) shouldEqual false
}
"create copy with another piece" in {
val bis = BiS(Seq(Fixtures.lootLegs))
val newBis = bis.withPiece(Fixtures.lootHands)
newBis.legs shouldEqual Some(Fixtures.lootLegs)
newBis.hands shouldEqual Some(Fixtures.lootHands)
newBis.pieces.length shouldEqual 2
}
"create copy without piece" in {
val bis = BiS(Seq(Fixtures.lootHands, Fixtures.lootLegs))
val newBis = bis.withoutPiece(Fixtures.lootHands)
newBis.legs shouldEqual Some(Fixtures.lootLegs)
newBis.hands shouldEqual None
newBis.pieces.length shouldEqual 1
}
"ignore upgrade on modification" in {
Fixtures.bis.withPiece(Fixtures.lootUpgrade) shouldEqual Fixtures.bis
Fixtures.bis.withoutPiece(Fixtures.lootUpgrade) shouldEqual Fixtures.bis
}
"return upgrade list" in {
Compare.mapEquals(Fixtures.bis.upgrades, Map[PieceUpgrade, Int](BodyUpgrade -> 2, AccessoryUpgrade -> 4)) shouldEqual true
}
}
}

View File

@ -1,7 +1,5 @@
package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.service.Party
object Fixtures {
lazy val bis: BiS = BiS(
Seq(
@ -22,9 +20,15 @@ object Fixtures {
lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
lazy val lootBody: Piece = Body(isTome = false, Job.DNC)
lazy val lootHands: Piece = Hands(isTome = true, Job.DNC)
lazy val lootLegs: Piece = Legs(isTome = false, Job.DNC)
lazy val lootWeapon: Piece = Weapon(isTome = true, Job.AnyJob)
lazy val lootBody: Piece = Body(isTome = false, Job.AnyJob)
lazy val lootHands: Piece = Hands(isTome = true, Job.AnyJob)
lazy val lootWaist: Piece = Waist(isTome = true, Job.AnyJob)
lazy val lootLegs: Piece = Legs(isTome = false, Job.AnyJob)
lazy val lootEars: Piece = Ears(isTome = false, Job.AnyJob)
lazy val lootRing: Piece = Ring(isTome = true, Job.AnyJob)
lazy val lootLeftRing: Piece = Ring(isTome = true, Job.AnyJob, "leftRing")
lazy val lootRightRing: Piece = Ring(isTome = true, Job.AnyJob, "rightRing")
lazy val lootUpgrade: Piece = BodyUpgrade
lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade)

View File

@ -0,0 +1,26 @@
package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"job model" must {
"create job from string" in {
Job.jobs.foreach { job =>
Job.withName(job.toString) shouldEqual job
}
}
"return AnyJob on unknown job" in {
Job.withName("random string") shouldEqual Job.AnyJob
}
"equal AnyJob to others" in {
Job.jobs.foreach { job =>
Job.AnyJob shouldBe job
}
}
}
}

View File

@ -0,0 +1,51 @@
package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PartyTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
private val party =
Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty))
"party model" must {
"accept player with same party id" in {
noException should be thrownBy
Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty))
}
"fail on multiple party ids" in {
val anotherPlayer = Fixtures.playerEmpty.copy(partyId = Fixtures.partyId2)
an [IllegalArgumentException] should be thrownBy
Party(Fixtures.partyId, Seq.empty, Map(anotherPlayer.playerId -> anotherPlayer))
an [IllegalArgumentException] should be thrownBy
Party(Fixtures.partyId, Seq.empty, Map(Fixtures.playerEmpty.playerId -> Fixtures.playerEmpty, anotherPlayer.playerId -> anotherPlayer))
}
"return player list" in {
Compare.seqEquals(party.getPlayers, Seq(Fixtures.playerEmpty)) shouldEqual true
}
"return player" in {
party.player(Fixtures.playerEmpty.playerId) shouldEqual Some(Fixtures.playerEmpty)
}
"return empty on unknown player" in {
party.player(Fixtures.playerEmpty.copy(partyId = Fixtures.partyId2).playerId) shouldEqual None
}
"add new player" in {
val newParty = Party(Fixtures.partyId, Seq.empty, Map.empty)
newParty.withPlayer(Fixtures.playerEmpty) shouldEqual party
}
"reject player with another party id" in {
val anotherPlayer = Fixtures.playerEmpty.copy(partyId = Fixtures.partyId2)
party.withPlayer(anotherPlayer) shouldEqual party
}
}
}

View File

@ -0,0 +1,32 @@
package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"piece model" must {
"convert `isTome` property to string" in {
Fixtures.lootBody.isTomeToString shouldEqual "no"
Fixtures.lootHands.isTomeToString shouldEqual "yes"
}
"return upgrade" in {
Fixtures.lootWeapon.upgrade shouldEqual Some(WeaponUpgrade)
Fixtures.lootBody.upgrade shouldEqual None
Fixtures.lootHands.upgrade shouldEqual Some(BodyUpgrade)
Fixtures.lootWaist.upgrade shouldEqual Some(AccessoryUpgrade)
Fixtures.lootLegs.upgrade shouldEqual None
Fixtures.lootEars.upgrade shouldEqual None
Fixtures.lootLeftRing.upgrade shouldEqual Some(AccessoryUpgrade)
Fixtures.lootRightRing.upgrade shouldEqual Some(AccessoryUpgrade)
}
"build piece from string" in {
Fixtures.bis.pieces.foreach { piece =>
Piece(piece.piece, piece.isTome, piece.job) shouldEqual piece
}
}
}
}

View File

@ -0,0 +1,14 @@
package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PlayerIdTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"player id model" must {
"be parsed from string" in {
PlayerId(Fixtures.partyId, Fixtures.playerEmpty.playerId.toString) shouldEqual Some(Fixtures.playerEmpty.playerId)
}
}
}

View File

@ -0,0 +1,18 @@
package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"player model" must {
"add best in slot set" in {
Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)).bis shouldEqual Fixtures.bis
}
"add loot" in {
Fixtures.playerEmpty.withLoot(Some(Fixtures.loot)).loot shouldEqual Fixtures.loot
}
}
}

View File

@ -0,0 +1,24 @@
package me.arcanis.ffxivbis.models
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class UserTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
"user model" must {
"verify password" in {
Fixtures.userAdmin.verify(Fixtures.userPassword) shouldEqual true
Fixtures.userAdmin.verify(Fixtures.userPassword2) shouldEqual false
}
"verify scope" in {
Permission.values.foreach { permission =>
Fixtures.userAdmin.verityScope(permission) shouldEqual true
}
Permission.values.foreach { permission =>
Fixtures.userGet.verityScope(permission) shouldEqual (permission == Permission.get)
}
}
}
}

View File

@ -2,7 +2,7 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem
import akka.testkit.{ImplicitSender, TestKit}
import me.arcanis.ffxivbis.models.{Fixtures, Settings}
import me.arcanis.ffxivbis.models.{Fixtures, Party, Settings}
import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -1,6 +1,11 @@
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
}
def seqEquals[T](left: Seq[T], right: Seq[T]): Boolean =
left.groupBy(identity).view.mapValues(_.size).forall {
case (key, count) => right.count(_ == key) == count