multi item support

This commit is contained in:
Evgenii Alekseev 2020-12-03 03:35:06 +03:00
parent 25b05aa289
commit 2e16a8c1fa
24 changed files with 195 additions and 85 deletions

View File

@ -0,0 +1,2 @@
drop index bis_piece_player_id_idx;
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);

View File

@ -0,0 +1,2 @@
drop index bis_piece_player_id_idx;
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);

View File

@ -37,10 +37,13 @@ trait BiSHelper extends BisProviderHelper {
} }
def putBiS(playerId: PlayerId, link: String) def putBiS(playerId: PlayerId, link: String)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
(storage ? DatabaseBiSHandler.RemovePiecesFromBiS(playerId)).flatMap { _ =>
downloadBiS(link, playerId.job).flatMap { bis => downloadBiS(link, playerId.job).flatMap { bis =>
Future.traverse(bis.pieces)(addPieceBiS(playerId, _)) Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
}.map(_ => ()) }.map(_ => ())
}
}
def removePieceBiS(playerId: PlayerId, piece: Piece) def removePieceBiS(playerId: PlayerId, piece: Piece)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =

View File

@ -21,13 +21,15 @@ case class PlayerResponse(
@Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) { @Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) {
def toPlayer: Player = def toPlayer: Player =
Player(-1, partyId, Job.withName(job), nick, Player(-1, partyId, Job.withName(job), nick,
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), loot.getOrElse(Seq.empty).map(_.toLoot), BiS(bis.getOrElse(Seq.empty).map(_.toPiece)),
loot.getOrElse(Seq.empty).map(_.toLoot),
link, priority.getOrElse(0)) link, priority.getOrElse(0))
} }
object PlayerResponse { object PlayerResponse {
def fromPlayer(player: Player): PlayerResponse = def fromPlayer(player: Player): PlayerResponse =
PlayerResponse(player.partyId, player.job.toString, player.nick, PlayerResponse(player.partyId, player.job.toString, player.nick,
Some(player.bis.pieces.map(PieceResponse.fromPiece)), Some(player.loot.map(LootResponse.fromLoot)), Some(player.bis.pieces.map(PieceResponse.fromPiece)),
Some(player.loot.map(LootResponse.fromLoot)),
player.link, Some(player.priority)) player.link, Some(player.priority))
} }

View File

@ -113,7 +113,7 @@ object BiSView {
input(name:="add", id:="add", `type`:="submit", value:="add") input(name:="add", id:="add", `type`:="submit", value:="add")
), ),
form(action:="/bis", method:="post")( form(action:=s"/party/$partyId/bis", method:="post")(
select(name:="player", id:="player", title:="player") select(name:="player", id:="player", title:="player")
(for (player <- party) yield option(player.playerId.toString)), (for (player <- party) yield option(player.playerId.toString)),
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"), input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),

View File

@ -47,8 +47,8 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
post { post {
formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) { formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) {
(nick, job, maybePriority, maybeLink, action) => (nick, job, maybePriority, maybeLink, action) =>
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) { onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) { _ =>
case _ => redirect(s"/party/$partyId/players", StatusCodes.Found) redirect(s"/party/$partyId/players", StatusCodes.Found)
} }
} }
} }
@ -62,7 +62,7 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = { (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
def maybePlayerId = PlayerId(partyId, Some(nick), Some(job)) def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
def player(playerId: PlayerId) = def player(playerId: PlayerId) =
Player(-1, partyId, playerId.job, playerId.nick, BiS(), Seq.empty, maybeLink, maybePriority.getOrElse(0)) Player(-1, partyId, playerId.job, playerId.nick, BiS.empty, Seq.empty, maybeLink, maybePriority.getOrElse(0))
(action, maybePlayerId) match { (action, maybePlayerId) match {
case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ()) case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ())

View File

@ -8,21 +8,7 @@
*/ */
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
case class BiS(weapon: Option[Piece], case class BiS(pieces: Seq[Piece]) {
head: Option[Piece],
body: Option[Piece],
hands: Option[Piece],
waist: Option[Piece],
legs: Option[Piece],
feet: Option[Piece],
ears: Option[Piece],
neck: Option[Piece],
wrist: Option[Piece],
leftRing: Option[Piece],
rightRing: Option[Piece]) {
val pieces: Seq[Piece] =
Seq(weapon, head, body, hands, waist, legs, feet, ears, neck, wrist, leftRing, rightRing).flatten
def hasPiece(piece: Piece): Boolean = piece match { def hasPiece(piece: Piece): Boolean = piece match {
case upgrade: PieceUpgrade => upgrades.contains(upgrade) case upgrade: PieceUpgrade => upgrades.contains(upgrade)
@ -31,50 +17,27 @@ case class BiS(weapon: Option[Piece],
def upgrades: Map[PieceUpgrade, Int] = def upgrades: Map[PieceUpgrade, Int] =
pieces.groupBy(_.upgrade).foldLeft(Map.empty[PieceUpgrade, Int]) { pieces.groupBy(_.upgrade).foldLeft(Map.empty[PieceUpgrade, Int]) {
case (acc, (Some(k), v)) => acc + (k -> v.length) case (acc, (Some(k), v)) => acc + (k -> v.size)
case (acc, _) => acc case (acc, _) => acc
} withDefaultValue 0 } withDefaultValue 0
def withPiece(piece: Piece): BiS = copyWithPiece(piece.piece, Some(piece)) def withPiece(piece: Piece): BiS = copy(pieces :+ piece)
def withoutPiece(piece: Piece): BiS = copyWithPiece(piece.piece, None) def withoutPiece(piece: Piece): BiS = copy(pieces.filterNot(_.strictEqual(piece)))
private def copyWithPiece(name: String, piece: Option[Piece]): BiS = { override def equals(obj: Any): Boolean = {
val params = Map( def comparePieces(left: Seq[Piece], right: Seq[Piece]): Boolean =
"weapon" -> weapon, left.groupBy(identity).view.mapValues(_.size).forall {
"head" -> head, case (key, count) => right.count(_.strictEqual(key)) == count
"body" -> body, }
"hands" -> hands,
"waist" -> waist, obj match {
"legs" -> legs, case left: BiS => comparePieces(left.pieces, pieces)
"feet" -> feet, case _ => false
"ears" -> ears, }
"neck" -> neck,
"wrist" -> wrist,
"left ring" -> leftRing,
"right ring" -> rightRing
) + (name -> piece)
BiS(params)
} }
} }
object BiS { object BiS {
def apply(data: Map[String, Option[Piece]]): BiS =
BiS(
data.get("weapon").flatten,
data.get("head").flatten,
data.get("body").flatten,
data.get("hands").flatten,
data.get("waist").flatten,
data.get("legs").flatten,
data.get("feet").flatten,
data.get("ears").flatten,
data.get("neck").flatten,
data.get("wrist").flatten,
data.get("left ring").flatten,
data.get("right ring").flatten)
def apply(): BiS = BiS(Seq.empty) def empty: BiS = BiS(Seq.empty)
def apply(pieces: Seq[Piece]): BiS =
BiS(pieces.map(piece => piece.piece -> Some(piece)).toMap)
} }

View File

@ -9,6 +9,7 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
object Job { object Job {
sealed trait RightSide sealed trait RightSide
object AccessoriesDex extends RightSide object AccessoriesDex extends RightSide
object AccessoriesInt extends RightSide object AccessoriesInt extends RightSide
@ -26,6 +27,7 @@ object Job {
object BodyRanges extends LeftSide object BodyRanges extends LeftSide
sealed trait Job { sealed trait Job {
def leftSide: LeftSide def leftSide: LeftSide
def rightSide: RightSide def rightSide: RightSide

View File

@ -11,5 +11,6 @@ package me.arcanis.ffxivbis.models
import java.time.Instant import java.time.Instant
case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) { case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) {
def isFreeLootToString: String = if (isFreeLoot) "yes" else "no" def isFreeLootToString: String = if (isFreeLoot) "yes" else "no"
} }

View File

@ -36,6 +36,7 @@ case class Party(partyDescription: PartyDescription, rules: Seq[String], players
} }
object Party { object Party {
def apply(party: PartyDescription, config: Config, def apply(party: PartyDescription, config: Config,
players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = { players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = {
val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece))) val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece)))

View File

@ -9,9 +9,11 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
case class PartyDescription(partyId: String, partyAlias: Option[String]) { case class PartyDescription(partyId: String, partyAlias: Option[String]) {
def alias: String = partyAlias.getOrElse(partyId) def alias: String = partyAlias.getOrElse(partyId)
} }
object PartyDescription { object PartyDescription {
def empty(partyId: String): PartyDescription = PartyDescription(partyId, None) def empty(partyId: String): PartyDescription = PartyDescription(partyId, None)
} }

View File

@ -8,7 +8,8 @@
*/ */
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
sealed trait Piece { sealed trait Piece extends Equals {
def pieceType: PieceType.PieceType def pieceType: PieceType.PieceType
def job: Job.Job def job: Job.Job
def piece: String def piece: String
@ -22,6 +23,9 @@ sealed trait Piece {
case _: PieceBody => Some(BodyUpgrade) case _: PieceBody => Some(BodyUpgrade)
case _: PieceWeapon => Some(WeaponUpgrade) case _: PieceWeapon => Some(WeaponUpgrade)
} }
// used for ring comparison
def strictEqual(obj: Any): Boolean = equals(obj)
} }
trait PieceAccessory extends Piece trait PieceAccessory extends Piece
@ -78,10 +82,16 @@ case class Wrist(override val pieceType: PieceType.PieceType, override val job:
case class Ring(override val pieceType: PieceType.PieceType, override val job: Job.Job, override val piece: String = "ring") case class Ring(override val pieceType: PieceType.PieceType, override val job: Job.Job, override val piece: String = "ring")
extends PieceAccessory { extends PieceAccessory {
def withJob(other: Job.Job): Piece = copy(job = other) def withJob(other: Job.Job): Piece = copy(job = other)
override def equals(obj: Any): Boolean = obj match { override def equals(obj: Any): Boolean = obj match {
case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job) case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job)
case _ => false 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 { case object AccessoryUpgrade extends PieceUpgrade {

View File

@ -1,6 +1,7 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
object PieceType { object PieceType {
sealed trait PieceType sealed trait PieceType
case object Crafted extends PieceType case object Crafted extends PieceType

View File

@ -12,6 +12,7 @@ import scala.util.Try
import scala.util.matching.Regex import scala.util.matching.Regex
trait PlayerIdBase { trait PlayerIdBase {
def job: Job.Job def job: Job.Job
def nick: String def nick: String
@ -21,6 +22,7 @@ trait PlayerIdBase {
case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase
object PlayerId { object PlayerId {
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] = def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
(maybeNick, maybeJob) match { (maybeNick, maybeJob) match {
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption

View File

@ -38,6 +38,7 @@ case class PlayerIdWithCounters(partyId: String,
} }
object PlayerIdWithCounters { object PlayerIdWithCounters {
private case class PlayerCountersComparator(values: Int*) { private case class PlayerCountersComparator(values: Int*) {
def >(that: PlayerCountersComparator): Boolean = { def >(that: PlayerCountersComparator): Boolean = {
@scala.annotation.tailrec @scala.annotation.tailrec

View File

@ -29,6 +29,10 @@ trait DatabaseBiSHandler { this: Database =>
case RemovePieceFromBiS(playerId, piece) => case RemovePieceFromBiS(playerId, piece) =>
val client = sender() val client = sender()
profile.deletePieceBiS(playerId, piece).pipeTo(client) profile.deletePieceBiS(playerId, piece).pipeTo(client)
case RemovePiecesFromBiS(playerId) =>
val client = sender()
profile.deletePiecesBiS(playerId).pipeTo(client)
} }
} }
@ -40,4 +44,7 @@ object DatabaseBiSHandler {
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest { case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId override def partyId: String = playerId.partyId
} }
case class RemovePiecesFromBiS(playerId: PlayerId) extends Database.DatabaseRequest {
override def partyId: String = playerId.partyId
}
} }

View File

@ -46,14 +46,21 @@ trait BiSProfile { this: DatabaseProfile =>
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] = def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete) db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
def deletePiecesBiSById(playerId: Long): Future[Int] =
db.run(piecesBiS(Seq(playerId)).delete)
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId)) def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] = def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot)) db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot))
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] = def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece))) getPiecesBiSById(playerId).flatMap {
case pieces if pieces.exists(loot => loot.piece.strictEqual(piece)) => Future.successful(0)
case _ => db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
}
private def pieceBiS(piece: BiSRep) = private def pieceBiS(piece: BiSRep) =
piecesBiS(Seq(piece.playerId)).filter(_.piece === piece.piece) piecesBiS(Seq(piece.playerId)).filter { stored =>
(stored.piece === piece.piece) && (stored.pieceType === piece.pieceType)
}
private def piecesBiS(playerIds: Seq[Long]) = private def piecesBiS(playerIds: Seq[Long]) =
bisTable.filter(_.playerId.inSet(playerIds.toSet)) bisTable.filter(_.playerId.inSet(playerIds.toSet))
} }

View File

@ -36,6 +36,8 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
// generic bis api // generic bis api
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] = def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
byPlayerId(playerId, deletePieceBiSById(piece)) byPlayerId(playerId, deletePieceBiSById(piece))
def deletePiecesBiS(playerId: PlayerId): Future[Int] =
byPlayerId(playerId, deletePiecesBiSById)
def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] = def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] =
byPlayerId(playerId, getPiecesBiSById) byPlayerId(playerId, getPiecesBiSById)
def getPiecesBiS(partyId: String): Future[Seq[Loot]] = def getPiecesBiS(partyId: String): Future[Seq[Loot]] =
@ -57,7 +59,7 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
byPlayerId(playerId, insertPieceById(loot)) byPlayerId(playerId, insertPieceById(loot))
private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] = private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] =
getPlayers(partyId).map(callback).flatten getPlayers(partyId).flatMap(callback)
private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] = private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] =
getPlayer(playerId).flatMap { getPlayer(playerId).flatMap {
case Some(id) => callback(id) case Some(id) => callback(id)

View File

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

View File

@ -26,6 +26,7 @@ object Fixtures {
lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob) lazy val lootWeapon: Piece = Weapon(pieceType = PieceType.Tome, Job.AnyJob)
lazy val lootBody: Piece = Body(pieceType = PieceType.Savage, 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 lootHands: Piece = Hands(pieceType = PieceType.Tome, Job.AnyJob)
lazy val lootWaist: Piece = Waist(pieceType = PieceType.Tome, Job.AnyJob) lazy val lootWaist: Piece = Waist(pieceType = PieceType.Tome, Job.AnyJob)
lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob) lazy val lootLegs: Piece = Legs(pieceType = PieceType.Savage, Job.AnyJob)
@ -40,7 +41,7 @@ object Fixtures {
lazy val partyId2: String = Party.randomPartyId lazy val partyId2: String = Party.randomPartyId
lazy val playerEmpty: Player = lazy val playerEmpty: Player =
Player(1, partyId, Job.DNC, "Siuan Sanche", BiS(), Seq.empty, Some(link)) Player(1, partyId, Job.DNC, "Siuan Sanche", BiS.empty, Seq.empty, Some(link))
lazy val playerWithBiS: Player = playerEmpty.copy(bis = bis) lazy val playerWithBiS: Player = playerEmpty.copy(bis = bis)
lazy val userPassword: String = "password" lazy val userPassword: String = "password"

View File

@ -10,10 +10,11 @@ import akka.testkit.TestKit
import com.typesafe.config.Config import com.typesafe.config.Config
import me.arcanis.ffxivbis.{Fixtures, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.BiS import me.arcanis.ffxivbis.models.{BiS, Body, Job, PieceType}
import me.arcanis.ffxivbis.service.bis.BisProvider import me.arcanis.ffxivbis.service.bis.BisProvider
import me.arcanis.ffxivbis.service.{PartyService, impl} import me.arcanis.ffxivbis.service.{PartyService, impl}
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scala.concurrent.Await import scala.concurrent.Await
@ -48,6 +49,15 @@ class BiSEndpointTest extends WordSpec
Settings.clearDatabase(system.settings.config) Settings.clearDatabase(system.settings.config)
} }
private def compareBiSResponse(actual: PlayerResponse, expected: PlayerResponse): 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 { "api v1 bis endpoint" must {
"create best in slot set from ariyala" in { "create best in slot set from ariyala" in {
@ -61,11 +71,13 @@ class BiSEndpointTest extends WordSpec
"return best in slot set" in { "return best in slot set" in {
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job))) val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))) val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check { Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK status shouldEqual StatusCodes.OK
responseAs[Seq[PlayerResponse]] shouldEqual response val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
} }
} }
@ -80,11 +92,13 @@ class BiSEndpointTest extends WordSpec
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job))) val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val bis = BiS(Fixtures.bis.pieces.filterNot(_ == Fixtures.lootBody)) val bis = BiS(Fixtures.bis.pieces.filterNot(_ == Fixtures.lootBody))
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))) val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
Get(uri).withHeaders(auth) ~> route ~> check { Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK status shouldEqual StatusCodes.OK
responseAs[Seq[PlayerResponse]] shouldEqual response val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
} }
} }
@ -98,11 +112,102 @@ class BiSEndpointTest extends WordSpec
} }
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job))) val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = Seq(PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))) val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check { Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK status shouldEqual StatusCodes.OK
responseAs[Seq[PlayerResponse]] shouldEqual response val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"do not allow to add same item to best in slot set" in {
val piece = PieceResponse.fromPiece(Fixtures.lootBody.withJob(Job.DNC))
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
responseAs[String] shouldEqual ""
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"allow to add item with another type to best in slot set" in {
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
responseAs[String] shouldEqual ""
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val bis = Fixtures.bis.withPiece(piece.toPiece)
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"remove only specific item from best in slot set" in {
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
val entity = PieceActionResponse(ApiAction.remove, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
responseAs[String] shouldEqual ""
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
}
}
"totaly replace player bis" in {
// add random item first
val piece = PieceResponse.fromPiece(Fixtures.lootBodyCrafted.withJob(Job.DNC))
val entity = PieceActionResponse(ApiAction.add, piece, playerId, None)
Post(endpoint, entity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Accepted
responseAs[String] shouldEqual ""
}
val bisEntity = PlayerBiSLinkResponse(Fixtures.link, playerId)
Put(endpoint, bisEntity).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.Created
responseAs[String] shouldEqual ""
}
val uri = endpoint.withQuery(Uri.Query(Map("nick" -> playerId.nick, "job" -> playerId.job)))
val response = PlayerResponse.fromPlayer(Fixtures.playerEmpty.withBiS(Some(Fixtures.bis)))
Get(uri).withHeaders(auth) ~> route ~> check {
status shouldEqual StatusCodes.OK
val actual = responseAs[Seq[PlayerResponse]]
actual.length shouldEqual 1
actual.foreach(compareBiSResponse(_, response))
} }
} }

View File

@ -29,18 +29,14 @@ class BiSTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
val bis = BiS(Seq(Fixtures.lootLegs)) val bis = BiS(Seq(Fixtures.lootLegs))
val newBis = bis.withPiece(Fixtures.lootHands) val newBis = bis.withPiece(Fixtures.lootHands)
newBis.legs shouldEqual Some(Fixtures.lootLegs) newBis shouldEqual BiS(Seq(Fixtures.lootLegs, Fixtures.lootHands))
newBis.hands shouldEqual Some(Fixtures.lootHands)
newBis.pieces.length shouldEqual 2
} }
"create copy without piece" in { "create copy without piece" in {
val bis = BiS(Seq(Fixtures.lootHands, Fixtures.lootLegs)) val bis = BiS(Seq(Fixtures.lootHands, Fixtures.lootLegs))
val newBis = bis.withoutPiece(Fixtures.lootHands) val newBis = bis.withoutPiece(Fixtures.lootHands)
newBis.legs shouldEqual Some(Fixtures.lootLegs) newBis shouldEqual BiS(Seq(Fixtures.lootLegs))
newBis.hands shouldEqual None
newBis.pieces.length shouldEqual 1
} }
"ignore upgrade on modification" in { "ignore upgrade on modification" in {

View File

@ -72,7 +72,7 @@ class DatabaseBiSHandlerTest
database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None)
expectMsgPF(timeout) { expectMsgPF(timeout) {
case party: Seq[_] if partyBiSCompare(party, Seq(newPiece)) => () case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootHands, newPiece)) => ()
} }
} }

View File

@ -18,8 +18,8 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
import me.arcanis.ffxivbis.utils.Converters._ import me.arcanis.ffxivbis.utils.Converters._
private var default: Party = Party(PartyDescription.empty(Fixtures.partyId), Settings.config(Map.empty), Map.empty, Seq.empty, Seq.empty) private var default: Party = Party(PartyDescription.empty(Fixtures.partyId), Settings.config(Map.empty), Map.empty, Seq.empty, Seq.empty)
private var dnc: Player = Player(-1, Fixtures.partyId, Job.DNC, "a nick", BiS(), Seq.empty, Some(Fixtures.link)) private var dnc: Player = Player(-1, Fixtures.partyId, Job.DNC, "a nick", BiS.empty, Seq.empty, Some(Fixtures.link))
private var drg: Player = Player(-1, Fixtures.partyId, Job.DRG, "another nick", BiS(), Seq.empty, Some(Fixtures.link2)) private var drg: Player = Player(-1, Fixtures.partyId, Job.DRG, "another nick", BiS.empty, Seq.empty, Some(Fixtures.link2))
private val timeout: FiniteDuration = 60 seconds private val timeout: FiniteDuration = 60 seconds
override def beforeAll(): Unit = { override def beforeAll(): Unit = {
@ -51,7 +51,7 @@ class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
"suggest upgrade" in { "suggest upgrade" in {
val party = default.withPlayer( val party = default.withPlayer(
dnc.withBiS( dnc.withBiS(
Some(dnc.bis.copy(weapon = Some(Weapon(pieceType = PieceType.Tome, Job.DNC)))) Some(dnc.bis.withPiece(Weapon(pieceType = PieceType.Tome, Job.DNC)))
) )
) )