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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
package me.arcanis.ffxivbis.models
package me.arcanis.ffxivbis
import me.arcanis.ffxivbis.models._
object Fixtures {
lazy val bis: BiS = BiS(
@ -19,6 +21,7 @@ object Fixtures {
)
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 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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package me.arcanis.ffxivbis.models
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
@ -11,7 +12,7 @@ class PlayerTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
}
"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
import me.arcanis.ffxivbis.Fixtures
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
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.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 scala.concurrent.duration._

View File

@ -3,7 +3,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem
import akka.pattern.ask
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.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -3,7 +3,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem
import akka.pattern.ask
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.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

View File

@ -2,7 +2,8 @@ package me.arcanis.ffxivbis.service
import akka.actor.ActorSystem
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.utils.Compare
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}

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.{Fixtures, Settings}
import me.arcanis.ffxivbis.storage.Migration
import me.arcanis.ffxivbis.utils.Compare
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)
}