even more tests

This commit is contained in:
Evgenii Alekseev 2019-10-27 04:23:06 +03:00
parent 4cdcd80d51
commit 2a1eb9430e
20 changed files with 176 additions and 53 deletions

9
.travis.yml Normal file
View File

@ -0,0 +1,9 @@
sudo: required
language: generic
services:
- docker
script:
- docker run -it --rm mozilla/sbt sbt compile
- docker run -it --rm mozilla/sbt sbt test

View File

@ -1,16 +1,16 @@
me.arcanis.ffxivbis { me.arcanis.ffxivbis {
ariyala { ariyala {
// ariyala base url, string, required # ariyala base url, string, required
ariyala-url = "https://ffxiv.ariyala.com" ariyala-url = "https://ffxiv.ariyala.com"
// xivapi base url, string, required # xivapi base url, string, required
xivapi-url = "https://xivapi.com" xivapi-url = "https://xivapi.com"
// xivapi developer key, string, optional # xivapi developer key, string, optional
# xivapi-key = "abc-def" # xivapi-key = "abcdef"
} }
database { database {
// database section. Section must be declared inside # database section. Section must be declared inside
// for more detailed section descriptions refer to slick documentation # for more detailed section descriptions refer to slick documentation
mode = "sqlite" mode = "sqlite"
sqlite { sqlite {
@ -35,19 +35,19 @@ me.arcanis.ffxivbis {
} }
settings { settings {
// counters of Player class which will be called to sort players for loot priority # counters of Player class which will be called to sort players for loot priority
// list of strings, required # list of strings, required
priority = [ priority = [
"isRequired", "lootCountBiS", "priority", "lootCount", "lootCountTotal" "isRequired", "lootCountBiS", "priority", "lootCount", "lootCountTotal"
] ]
// general request timeout, duratin, required # general request timeout, duratin, required
request-timeout = 10s request-timeout = 10s
} }
web { web {
// address to bind, string, required # address to bind, string, required
host = "0.0.0.0" host = "0.0.0.0"
// port to bind, int, required # port to bind, int, required
port = 8000 port = 8000
} }
} }

View File

@ -50,7 +50,7 @@ object Party {
case (acc, (playerId, player)) => case (acc, (playerId, player)) =>
acc + (player.playerId -> player acc + (player.playerId -> player
.withBiS(bisByPlayer.get(playerId)) .withBiS(bisByPlayer.get(playerId))
.withLoot(lootByPlayer.get(playerId))) .withLoot(lootByPlayer.getOrElse(playerId, Seq.empty)))
} }
Party(partyId, getRules(config), playersWithItems) Party(partyId, getRules(config), playersWithItems)
} }

View File

@ -27,9 +27,10 @@ case class Player(partyId: String,
partyId, job, nick, isRequired(piece), priority, partyId, job, nick, isRequired(piece), priority,
bisCountTotal(piece), lootCount(piece), bisCountTotal(piece), lootCount(piece),
lootCountBiS(piece), lootCountTotal(piece)) lootCountBiS(piece), lootCountTotal(piece))
def withLoot(list: Option[Seq[Piece]]): Player = list match { def withLoot(piece: Piece): Player = withLoot(Seq(piece))
case Some(value) => copy(loot = value) def withLoot(list: Seq[Piece]): Player = list match {
case None => this case Nil => this
case _ => copy(loot = list)
} }
def isRequired(piece: Option[Piece]): Boolean = { def isRequired(piece: Option[Piece]): Boolean = {

View File

@ -26,12 +26,12 @@ case class PlayerIdWithCounters(partyId: String,
def playerId: PlayerId = PlayerId(partyId, job, nick) def playerId: PlayerId = PlayerId(partyId, job, nick)
private val counters: Map[String, Int] = Map( private val counters: Map[String, Int] = Map(
"isRequired" -> (if (isRequired) 1 else 0), "isRequired" -> (if (isRequired) 1 else 0), // true has more priority
"priority" -> priority, "priority" -> -priority, // the less value the more priority
"bisCountTotal" -> bisCountTotal, "bisCountTotal" -> bisCountTotal, // the more pieces in bis the more priority
"lootCount" -> lootCount, "lootCount" -> -lootCount, // the less loot got the more priority
"lootCountBiS" -> lootCountBiS, "lootCountBiS" -> -lootCountBiS, // the less bis pieces looted the more priority
"lootCountTotal" -> lootCountTotal) withDefaultValue 0 "lootCountTotal" -> -lootCountTotal) withDefaultValue 0 // the less pieces looted the more priority
private def withCounters(orderBy: Seq[String]): PlayerCountersComparator = private def withCounters(orderBy: Seq[String]): PlayerCountersComparator =
PlayerCountersComparator(orderBy.map(counters): _*) PlayerCountersComparator(orderBy.map(counters): _*)

View File

@ -22,7 +22,7 @@ import me.arcanis.ffxivbis.models.{BiS, Job, Piece}
import spray.json._ import spray.json._
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try import scala.util.{Failure, Success, Try}
class Ariyala extends Actor with StrictLogging { class Ariyala extends Actor with StrictLogging {
import Ariyala._ import Ariyala._
@ -51,12 +51,16 @@ class Ariyala extends Actor with StrictLogging {
sendRequest(uri, Ariyala.parseAriyalaJsonToPieces(job, getIsTome)) sendRequest(uri, Ariyala.parseAriyalaJsonToPieces(job, getIsTome))
} }
private def getIsTome(itemId: Long): Future[Boolean] = { private def getIsTome(itemIds: Seq[Long]): Future[Map[Long, Boolean]] = {
val uri = Try(Uri(xivapiUrl) val uri = Uri(xivapiUrl)
.withPath(Uri.Path / "item" / itemId.toString) .withPath(Uri.Path / "item")
.withQuery(Uri.Query(Map("columns" -> "IsEquippable", "private_key" -> xivapiKey.getOrElse(""))))) .withQuery(Uri.Query(Map(
"columns" -> Seq("ID", "IsEquippable").mkString(","),
"ids" -> itemIds.mkString(","),
"private_key" -> xivapiKey.getOrElse("")
)))
sendRequest(uri.toOption.get, Ariyala.parseXivapiJson) sendRequest(uri, Ariyala.parseXivapiJson)
} }
private def sendRequest[T](uri: Uri, parser: JsObject => Future[T]): Future[T] = private def sendRequest[T](uri: Uri, parser: JsObject => Future[T]): Future[T] =
@ -68,7 +72,7 @@ class Ariyala extends Actor with StrictLogging {
.map(result => parser(result.parseJson.asJsObject)) .map(result => parser(result.parseJson.asJsObject))
.toMat(Sink.head)(Keep.right) .toMat(Sink.head)(Keep.right)
.run().flatten .run().flatten
case _ => Future.failed(deserializationError("Invalid response from server")) case other => Future.failed(new Error(s"Invalid response from server $other"))
}.flatten }.flatten
} }
@ -77,13 +81,14 @@ object Ariyala {
case class GetBiS(link: String, job: Job.Job) case class GetBiS(link: String, job: Job.Job)
private def parseAriyalaJson(job: Job.Job)(js: JsObject): Future[Map[String, Long]] = { private def parseAriyalaJson(job: Job.Job)(js: JsObject)
try { (implicit executionContext: ExecutionContext): Future[Map[String, Long]] =
Future {
val apiJob = js.fields.get("content") match { val apiJob = js.fields.get("content") match {
case Some(JsString(value)) => value case Some(JsString(value)) => value
case other => throw deserializationError(s"Invalid job name $other") case other => throw deserializationError(s"Invalid job name $other")
} }
Future.successful(js.fields.get("datasets") match { js.fields.get("datasets") match {
case Some(datasets: JsObject) => case Some(datasets: JsObject) =>
val fields = datasets.fields val fields = datasets.fields
fields.getOrElse(apiJob, fields(job.toString)).asJsObject fields.getOrElse(apiJob, fields(job.toString)).asJsObject
@ -94,24 +99,30 @@ object Ariyala {
case (acc, _) => acc case (acc, _) => acc
} }
case other => throw deserializationError(s"Invalid json $other") case other => throw deserializationError(s"Invalid json $other")
}) }
} catch {
case e: Exception => Future.failed(e)
} }
}
private def parseAriyalaJsonToPieces(job: Job.Job, isTome: Long => Future[Boolean])(js: JsObject) private def parseAriyalaJsonToPieces(job: Job.Job, isTome: Seq[Long] => Future[Map[Long, Boolean]])(js: JsObject)
(implicit executionContext: ExecutionContext): Future[Seq[Piece]] = (implicit executionContext: ExecutionContext): Future[Seq[Piece]] =
parseAriyalaJson(job)(js).map { pieces => parseAriyalaJson(job)(js).flatMap { pieces =>
Future.sequence(pieces.toSeq.map { isTome(pieces.values.toSeq).map { tomePieces =>
case (itemName, itemId) => isTome(itemId).map(Piece(itemName, _, job)) pieces.view.mapValues(tomePieces).map {
}) case (piece, isTomePiece) => Piece(piece, isTomePiece, job)
}.flatten }.toSeq
}
}
private def parseXivapiJson(js: JsObject): Future[Boolean] = private def parseXivapiJson(js: JsObject)
js.fields.get("IsEquippable") match { (implicit executionContext: ExecutionContext): Future[Map[Long, Boolean]] =
case Some(JsNumber(value)) => Future.successful(value == 0) // don't ask Future {
case other => Future.failed(deserializationError(s"Could not parse $other")) js.fields("Results") match {
case array: JsArray =>
array.elements.map(_.asJsObject.getFields("ID", "IsEquippable") match {
case Seq(JsNumber(id), JsNumber(isTome)) => id.toLong -> (isTome == 0)
case other => throw deserializationError(s"Could not parse $other")
}).toMap
case other => throw deserializationError(s"Could not parse $other")
}
} }
private def remapKey(key: String): Option[String] = key match { private def remapKey(key: String): Option[String] = key match {

View File

@ -1,4 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis
import me.arcanis.ffxivbis.models._
object Fixtures { object Fixtures {
lazy val bis: BiS = BiS( lazy val bis: BiS = BiS(
@ -19,6 +21,7 @@ object Fixtures {
) )
lazy val link: String = "https://ffxiv.ariyala.com/19V5R" lazy val link: String = "https://ffxiv.ariyala.com/19V5R"
lazy val link2: String = "https://ffxiv.ariyala.com/1A0WM"
lazy val lootWeapon: Piece = Weapon(isTome = true, Job.AnyJob) lazy val lootWeapon: Piece = Weapon(isTome = true, Job.AnyJob)
lazy val lootBody: Piece = Body(isTome = false, Job.AnyJob) lazy val lootBody: Piece = Body(isTome = false, Job.AnyJob)

View File

@ -1,4 +1,4 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis
import java.io.File import java.io.File

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PieceTest extends WordSpecLike with Matchers with BeforeAndAfterAll {

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PlayerIdTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PlayerIdTest extends WordSpecLike with Matchers with BeforeAndAfterAll {

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
@ -11,7 +12,7 @@ class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
} }
"add loot" in { "add loot" in {
Fixtures.playerEmpty.withLoot(Some(Fixtures.loot)).loot shouldEqual Fixtures.loot Fixtures.playerEmpty.withLoot(Fixtures.loot).loot shouldEqual Fixtures.loot
} }
} }

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class UserTest extends WordSpecLike with Matchers with BeforeAndAfterAll { class UserTest extends WordSpecLike with Matchers with BeforeAndAfterAll {

View File

@ -2,7 +2,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.testkit.{ImplicitSender, TestKit} import akka.testkit.{ImplicitSender, TestKit}
import me.arcanis.ffxivbis.models.{Fixtures, Job} import me.arcanis.ffxivbis.Fixtures
import me.arcanis.ffxivbis.models._
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
import scala.concurrent.duration._ import scala.concurrent.duration._

View File

@ -3,7 +3,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.pattern.ask import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestKit} import akka.testkit.{ImplicitSender, TestKit}
import me.arcanis.ffxivbis.models.{Fixtures, Hands, Job, Piece, Player, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -3,7 +3,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.pattern.ask import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestKit} import akka.testkit.{ImplicitSender, TestKit}
import me.arcanis.ffxivbis.models.{Fixtures, Piece, Player, Settings} import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._
import me.arcanis.ffxivbis.storage.Migration import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare import me.arcanis.ffxivbis.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

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

View File

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

View File

@ -0,0 +1,89 @@
package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem
import akka.pattern.ask
import akka.testkit.{ImplicitSender, TestKit}
import me.arcanis.ffxivbis.{Fixtures, Settings}
import me.arcanis.ffxivbis.models._
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.postfixOps
class LootSelectorTest extends TestKit(ActorSystem("lootselector"))
with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll {
private var default: Party = Party(Some(Fixtures.partyId), Settings.config(Map.empty))
private var dnc: Player = Player(Fixtures.partyId, Job.DNC, "a nick", BiS(), Seq.empty, Some(Fixtures.link))
private var drg: Player = Player(Fixtures.partyId, Job.DRG, "another nick", BiS(), Seq.empty, Some(Fixtures.link2))
private val timeout: FiniteDuration = 60 seconds
override def beforeAll(): Unit = {
val ariyala = system.actorOf(Ariyala.props)
val dncSet = Await.result((ariyala ? Ariyala.GetBiS(Fixtures.link, Job.DNC) )(timeout).mapTo[BiS], timeout)
dnc = dnc.withBiS(Some(dncSet))
val drgSet = Await.result((ariyala ? Ariyala.GetBiS(Fixtures.link2, Job.DRG) )(timeout).mapTo[BiS], timeout)
drg = drg.withBiS(Some(drgSet))
default = default.withPlayer(dnc).withPlayer(drg)
system.stop(ariyala)
}
"loot selector" must {
"suggest loot by isRequired" in {
toPlayerId(default.suggestLoot(Head(isTome = false, Job.AnyJob))) shouldEqual Seq(dnc.playerId, drg.playerId)
}
"suggest loot if a player already have it" in {
val piece = Body(isTome = false, Job.AnyJob)
val party = default.withPlayer(dnc.withLoot(piece))
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
}
"suggest upgrade" in {
val party = default.withPlayer(
dnc.withBiS(
Some(dnc.bis.copy(weapon = Some(Weapon(isTome = true, Job.DNC))))
)
)
toPlayerId(party.suggestLoot(WeaponUpgrade)) shouldEqual Seq(dnc.playerId, drg.playerId)
}
"suggest loot by priority" in {
val party = default.withPlayer(dnc.copy(priority = 2))
toPlayerId(party.suggestLoot(Body(isTome = false, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
}
"suggest loot by bis pieces got" in {
val party = default.withPlayer(dnc.withLoot(Head(isTome = false, Job.AnyJob)))
toPlayerId(party.suggestLoot(Body(isTome = false, Job.AnyJob))) shouldEqual Seq(drg.playerId, dnc.playerId)
}
"suggest loot by this piece got" in {
val piece = Body(isTome = true, 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(isTome = true, Job.AnyJob)
val party = default
.withPlayer(dnc.withLoot(Seq(piece, piece)))
.withPlayer(drg.withLoot(piece))
toPlayerId(party.suggestLoot(piece)) shouldEqual Seq(drg.playerId, dnc.playerId)
}
}
private def toPlayerId(result: LootSelector.LootSelectorResult): Seq[PlayerId] = result.result.map(_.playerId)
}