From b228595a1b45d59c53986c4f44a9dd620637b1f7 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Thu, 24 Oct 2019 08:20:03 +0300 Subject: [PATCH] some tests --- build.sbt | 23 ----- libraries.sbt | 19 ++++ .../postgresql/V1_0__Create_tables.sql | 36 ++++++++ .../{ => sqlite}/V1_0__Create_tables.sql | 6 +- src/main/resources/logback.xml | 5 ++ src/main/resources/reference.conf | 10 +++ .../arcanis/ffxivbis/http/AriyalaHelper.scala | 2 +- .../arcanis/ffxivbis/http/PlayerHelper.scala | 2 +- .../me/arcanis/ffxivbis/http/UserHelper.scala | 2 +- .../me/arcanis/ffxivbis/models/User.scala | 1 + .../me/arcanis/ffxivbis/service/Ariyala.scala | 4 +- .../arcanis/ffxivbis/service/Database.scala | 5 ++ .../service/impl/DatabasePartyHandler.scala | 17 ++-- .../service/impl/DatabaseUserHandler.scala | 12 +-- .../ffxivbis/storage/LootProfile.scala | 5 +- .../arcanis/ffxivbis/storage/Migration.scala | 12 ++- .../ffxivbis/storage/PlayersProfile.scala | 2 + .../ffxivbis/storage/UsersProfile.scala | 6 +- .../me/arcanis/ffxivbis/models/Fixtures.scala | 43 ++++++++++ .../me/arcanis/ffxivbis/models/Settings.scala | 29 +++++++ .../ffxivbis/service/AriyalaTest.scala | 27 ++++++ .../service/DatabaseBiSHandlerTest.scala | 82 ++++++++++++++++++ .../service/DatabaseLootHandlerTest.scala | 86 +++++++++++++++++++ .../service/DatabasePartyHandlerTest.scala | 73 ++++++++++++++++ .../service/DatabaseUserHandlerTest.scala | 76 ++++++++++++++++ .../me/arcanis/ffxivbis/utils/Compare.scala | 8 ++ test.sbt | 4 + version.sbt | 1 + 28 files changed, 551 insertions(+), 47 deletions(-) create mode 100644 libraries.sbt create mode 100644 src/main/resources/db/migration/postgresql/V1_0__Create_tables.sql rename src/main/resources/db/migration/{ => sqlite}/V1_0__Create_tables.sql (88%) create mode 100644 src/main/resources/logback.xml create mode 100644 src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/models/Settings.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/service/DatabaseBiSHandlerTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/service/DatabasePartyHandlerTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/service/DatabaseUserHandlerTest.scala create mode 100644 src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala create mode 100644 test.sbt create mode 100644 version.sbt diff --git a/build.sbt b/build.sbt index 3c20904..0d1f248 100644 --- a/build.sbt +++ b/build.sbt @@ -1,28 +1,5 @@ name := "ffxivbis" -version := "0.9.0" - scalaVersion := "2.13.1" scalacOptions ++= Seq("-deprecation", "-feature") - -libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" -libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2" - -libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.10" -libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.10" -libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23" -libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4" -libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1" - -libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" -libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.7.0" - -libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.2" -libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2" -libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6" -libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.28.0" -libraryDependencies += "org.postgresql" % "postgresql" % "9.3-1104-jdbc4" - -libraryDependencies += "org.mindrot" % "jbcrypt" % "0.3m" - diff --git a/libraries.sbt b/libraries.sbt new file mode 100644 index 0000000..dbe25f3 --- /dev/null +++ b/libraries.sbt @@ -0,0 +1,19 @@ +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" +libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2" + +libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.10" +libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.10" +libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23" +libraryDependencies += "com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.4" +libraryDependencies += "javax.ws.rs" % "javax.ws.rs-api" % "2.1.1" + +libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" +libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.7.0" + +libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.2" +libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2" +libraryDependencies += "org.flywaydb" % "flyway-core" % "6.0.6" +libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.28.0" +libraryDependencies += "org.postgresql" % "postgresql" % "9.3-1104-jdbc4" + +libraryDependencies += "org.mindrot" % "jbcrypt" % "0.3m" diff --git a/src/main/resources/db/migration/postgresql/V1_0__Create_tables.sql b/src/main/resources/db/migration/postgresql/V1_0__Create_tables.sql new file mode 100644 index 0000000..a4741c4 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V1_0__Create_tables.sql @@ -0,0 +1,36 @@ +create table players ( + party_id text not null, + player_id bigserial, + created bigint not null, + nick text not null, + job text not null, + bis_link text, + priority integer not null default 1); +create unique index players_nick_job_idx on players(party_id, nick, job); + +create table loot ( + loot_id bigserial, + player_id bigint not null, + created bigint not null, + piece text not null, + is_tome integer not null, + job text not null, + foreign key (player_id) references players(player_id) on delete cascade); +create index loot_owner_idx on loot(player_id); + +create table bis ( + player_id bigint not null, + created bigint not null, + piece text not null, + is_tome integer not null, + job text not null, + foreign key (player_id) references players(player_id) on delete cascade); +create unique index bis_piece_player_id_idx on bis(player_id, piece); + +create table users ( + party_id text not null, + user_id bigserial, + username text not null, + password text not null, + permission text not null); +create unique index users_username_idx on users(party_id, username); diff --git a/src/main/resources/db/migration/V1_0__Create_tables.sql b/src/main/resources/db/migration/sqlite/V1_0__Create_tables.sql similarity index 88% rename from src/main/resources/db/migration/V1_0__Create_tables.sql rename to src/main/resources/db/migration/sqlite/V1_0__Create_tables.sql index ec21e44..26ee583 100644 --- a/src/main/resources/db/migration/V1_0__Create_tables.sql +++ b/src/main/resources/db/migration/sqlite/V1_0__Create_tables.sql @@ -1,6 +1,6 @@ create table players ( party_id text not null, - player_id integer primary key, + player_id integer primary key autoincrement, created integer not null, nick text not null, job text not null, @@ -9,7 +9,7 @@ create table players ( create unique index players_nick_job_idx on players(party_id, nick, job); create table loot ( - loot_id integer primary key, + loot_id integer primary key autoincrement, player_id integer not null, created integer not null, piece text not null, @@ -29,7 +29,7 @@ create unique index bis_piece_player_id_idx on bis(player_id, piece); create table users ( party_id text not null, - user_id integer primary key, + user_id integer primary key autoincrement, username text not null, password text not null, permission text not null); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..20a0ca5 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index e1432b4..5e22871 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -22,6 +22,16 @@ me.arcanis.ffxivbis { } numThreads = 10 } + + postgresql { + profile = "slick.jdbc.PostgresProfile$" + db { + url = "jdbc:postgresql://localhost/ffxivbis" + user = "user" + password = "password" + } + numThreads = 10 + } } settings { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala index 74dcd66..bfb19da 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/AriyalaHelper.scala @@ -20,5 +20,5 @@ class AriyalaHelper(ariyala: ActorRef) { def downloadBiS(link: String, job: Job.Job) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[BiS] = - (ariyala ? Ariyala.GetBiS(link, job)).mapTo[Seq[Piece]].map(BiS(_)) + (ariyala ? Ariyala.GetBiS(link, job)).mapTo[BiS] } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala index 8796aa9..50dfc46 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala @@ -36,7 +36,7 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = maybePlayerId match { case Some(playerId) => - (storage ? DatabasePartyHandler.GetPlayer(playerId)).mapTo[Player].map(Seq(_)) + (storage ? DatabasePartyHandler.GetPlayer(playerId)).mapTo[Option[Player]].map(_.toSeq) case None => (storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq) } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala index 12ad163..b426499 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/UserHelper.scala @@ -20,7 +20,7 @@ class UserHelper(storage: ActorRef) { def addUser(user: User, isHashedPassword: Boolean) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = - (storage ? DatabaseUserHandler.InsertUser(user, isHashedPassword)).mapTo[Int] + (storage ? DatabaseUserHandler.AddUser(user, isHashedPassword)).mapTo[Int] def user(partyId: String, username: String) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] = diff --git a/src/main/scala/me/arcanis/ffxivbis/models/User.scala b/src/main/scala/me/arcanis/ffxivbis/models/User.scala index 9a0e613..d15ada3 100644 --- a/src/main/scala/me/arcanis/ffxivbis/models/User.scala +++ b/src/main/scala/me/arcanis/ffxivbis/models/User.scala @@ -22,4 +22,5 @@ case class User(partyId: String, def hash: String = BCrypt.hashpw(password, BCrypt.gensalt) def verify(plain: String): Boolean = BCrypt.checkpw(plain, password) def verityScope(scope: Permission.Value): Boolean = permission >= scope + def withHashedPassword: User = copy(password = hash) } diff --git a/src/main/scala/me/arcanis/ffxivbis/service/Ariyala.scala b/src/main/scala/me/arcanis/ffxivbis/service/Ariyala.scala index cceba04..b13fd4e 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/Ariyala.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/Ariyala.scala @@ -18,7 +18,7 @@ import akka.stream.ActorMaterializer import akka.stream.scaladsl.{Keep, Sink} import akka.util.ByteString import com.typesafe.scalalogging.StrictLogging -import me.arcanis.ffxivbis.models.{Job, Piece} +import me.arcanis.ffxivbis.models.{BiS, Job, Piece} import spray.json._ import scala.concurrent.{ExecutionContext, Future} @@ -39,7 +39,7 @@ class Ariyala extends Actor with StrictLogging { override def receive: Receive = { case GetBiS(link, job) => val client = sender() - get(link, job).pipeTo(client) + get(link, job).map(BiS(_)).pipeTo(client) } private def get(link: String, job: Job.Job): Future[Seq[Piece]] = { diff --git a/src/main/scala/me/arcanis/ffxivbis/service/Database.scala b/src/main/scala/me/arcanis/ffxivbis/service/Database.scala index ea7dca4..79f9060 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/Database.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/Database.scala @@ -19,6 +19,11 @@ trait Database extends Actor with StrictLogging { implicit def executionContext: ExecutionContext def profile: DatabaseProfile + override def postStop(): Unit = { + profile.db.close() + super.postStop() + } + def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] = (party, maybePlayerId) match { case (_, Some(playerId)) => party.player(playerId).map(Seq(_)).getOrElse(Seq.empty) diff --git a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala index 09d2bb2..3b86b2a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabasePartyHandler.scala @@ -12,6 +12,8 @@ import akka.pattern.pipe import me.arcanis.ffxivbis.models.{BiS, Player, PlayerId} import me.arcanis.ffxivbis.service.Database +import scala.concurrent.Future + trait DatabasePartyHandler { this: Database => import DatabasePartyHandler._ @@ -26,11 +28,16 @@ trait DatabasePartyHandler { this: Database => case GetPlayer(playerId) => val client = sender() - val player = for { - bis <- profile.getPiecesBiS(playerId) - loot <- profile.getPieces(playerId) - } yield Player(playerId.partyId, playerId.job, playerId.nick, - BiS(bis.map(_.piece)), loot.map(_.piece)) + val player = profile.getPlayerFull(playerId).flatMap { maybePlayerData => + Future.traverse(maybePlayerData.toSeq) { playerData => + for { + bis <- profile.getPiecesBiS(playerId) + loot <- profile.getPieces(playerId) + } yield Player(playerId.partyId, playerId.job, playerId.nick, + BiS(bis.map(_.piece)), loot.map(_.piece), + playerData.link, playerData.priority) + } + }.map(_.headOption) player.pipeTo(client) case RemovePlayer(playerId) => diff --git a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseUserHandler.scala b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseUserHandler.scala index 0c7c916..8298120 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseUserHandler.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/impl/DatabaseUserHandler.scala @@ -16,6 +16,11 @@ trait DatabaseUserHandler { this: Database => import DatabaseUserHandler._ def userHandler: Receive = { + case AddUser(user, isHashedPassword) => + val client = sender() + val toInsert = if (isHashedPassword) user else user.withHashedPassword + profile.insertUser(toInsert).pipeTo(client) + case DeleteUser(partyId, username) => val client = sender() profile.deleteUser(partyId, username).pipeTo(client) @@ -27,17 +32,12 @@ trait DatabaseUserHandler { this: Database => case GetUsers(partyId) => val client = sender() profile.getUsers(partyId).pipeTo(client) - - case InsertUser(user, isHashedPassword) => - val client = sender() - val toInsert = if (isHashedPassword) user else user.copy(password = user.hash) - profile.insertUser(toInsert).pipeTo(client) } } object DatabaseUserHandler { + case class AddUser(user: User, isHashedPassword: Boolean) case class DeleteUser(partyId: String, username: String) case class GetUser(partyId: String, username: String) case class GetUsers(partyId: String) - case class InsertUser(user: User, isHashedPassword: Boolean) } diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala index 95e4c25..b395872 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/LootProfile.scala @@ -44,7 +44,10 @@ trait LootProfile { this: DatabaseProfile => } def deletePieceById(piece: Piece)(playerId: Long): Future[Int] = - db.run(pieceLoot(LootRep.fromPiece(playerId, piece)).take(1).delete) + db.run(pieceLoot(LootRep.fromPiece(playerId, piece)).map(_.lootId).max.result).flatMap { + case Some(id) => db.run(lootTable.filter(_.lootId === id).delete) + case _ => throw new IllegalArgumentException(s"Could not find piece $piece belong to $playerId") + } def getPiecesById(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId)) def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] = db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot)) diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala b/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala index 3458a2c..25af3d9 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/Migration.scala @@ -10,6 +10,7 @@ package me.arcanis.ffxivbis.storage import com.typesafe.config.Config import org.flywaydb.core.Flyway +import org.flywaydb.core.api.configuration.ClassicConfiguration import scala.concurrent.Future @@ -21,7 +22,16 @@ class Migration(config: Config) { val username = section.getString("db.user") val password = section.getString("db.password") - val flyway = Flyway.configure().dataSource(url, username, password).load() + val provider = url match { + case s"jdbc:$p:$_" => p + case other => throw new NotImplementedError(s"unknown could not parse jdbc url from $other") + } + + val flywayConfiguration = new ClassicConfiguration + flywayConfiguration.setLocationsAsStrings(s"db/migration/$provider") + flywayConfiguration.setDataSource(url, username, password) + val flyway = new Flyway(flywayConfiguration) + Future.successful(flyway.migrate()) } } diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala index 4dfe571..6fd4079 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/PlayersProfile.scala @@ -49,6 +49,8 @@ trait PlayersProfile { this: DatabaseProfile => }) def getPlayer(playerId: PlayerId): Future[Option[Long]] = db.run(player(playerId).map(_.playerId).result.headOption) + def getPlayerFull(playerId: PlayerId): Future[Option[Player]] = + db.run(player(playerId).result.headOption.map(_.map(_.toPlayer))) def getPlayers(partyId: String): Future[Seq[Long]] = db.run(players(partyId).map(_.playerId).result) def insertPlayer(playerObj: Player): Future[Int] = diff --git a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala index 373d15d..8680d48 100644 --- a/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala +++ b/src/main/scala/me/arcanis/ffxivbis/storage/UsersProfile.scala @@ -22,7 +22,7 @@ trait UsersProfile { this: DatabaseProfile => } object UserRep { def fromUser(user: User, id: Option[Long]): UserRep = - UserRep(user.partyId, None, user.username, user.password, user.permission.toString) + UserRep(user.partyId, id, user.username, user.password, user.permission.toString) } class Users(tag: Tag) extends Table[UserRep](tag, "users") { @@ -47,8 +47,8 @@ trait UsersProfile { this: DatabaseProfile => def getUsers(partyId: String): Future[Seq[User]] = db.run(user(partyId, None).result).map(_.map(_.toUser)) def insertUser(userObj: User): Future[Int] = - db.run(user(userObj.partyId, Some(userObj.username)).result.headOption).map { - case Some(user) => db.run(usersTable.update(UserRep.fromUser(userObj, user.userId))) + db.run(user(userObj.partyId, Some(userObj.username)).map(_.userId).result.headOption).map { + case Some(id) => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, Some(id)))) case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None))) }.flatten diff --git a/src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala b/src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala new file mode 100644 index 0000000..ab09eee --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala @@ -0,0 +1,43 @@ +package me.arcanis.ffxivbis.models + +import me.arcanis.ffxivbis.service.Party + +object Fixtures { + lazy val bis: BiS = BiS( + Seq( + Weapon(isTome = false ,Job.DNC), + Head(isTome = false, Job.DNC), + Body(isTome = false, Job.DNC), + Hands(isTome = true, Job.DNC), + Waist(isTome = true, Job.DNC), + Legs(isTome = true, Job.DNC), + Feet(isTome = false, Job.DNC), + Ears(isTome = false, Job.DNC), + Neck(isTome = true, Job.DNC), + Wrist(isTome = false, Job.DNC), + Ring(isTome = true, Job.DNC, "leftRing"), + Ring(isTome = true, Job.DNC, "rightRing") + ) + ) + + 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 lootUpgrade: Piece = BodyUpgrade + lazy val loot: Seq[Piece] = Seq(lootBody, lootHands, lootLegs, lootUpgrade) + + lazy val partyId: String = Party.randomPartyId + lazy val partyId2: String = Party.randomPartyId + + lazy val playerEmpty: Player = + Player(partyId, Job.DNC, "Siuan Sanche", BiS(), Seq.empty, Some(link)) + lazy val playerWithBiS: Player = playerEmpty.copy(bis = bis) + + lazy val userPassword: String = "password" + lazy val userPassword2: String = "pa55w0rd" + lazy val userAdmin: User = User(partyId, "admin", userPassword, Permission.admin).withHashedPassword + lazy val userGet: User = User(partyId, "get", userPassword, Permission.get).withHashedPassword + lazy val users: Seq[User] = Seq(userAdmin, userGet) +} diff --git a/src/test/scala/me/arcanis/ffxivbis/models/Settings.scala b/src/test/scala/me/arcanis/ffxivbis/models/Settings.scala new file mode 100644 index 0000000..c9ff3f1 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/models/Settings.scala @@ -0,0 +1,29 @@ +package me.arcanis.ffxivbis.models + +import java.io.File + +import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory} + +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) + } + + val default = ConfigFactory.load() + replace(default, values.toList) + } + + def clearDatabase(config: Config): Unit = { + val databasePath = + config.getString("me.arcanis.ffxivbis.database.sqlite.db.url").split(":").last + val databaseFile = new File(databasePath) + if (databaseFile.exists) + databaseFile.delete() + } + def randomDatabasePath: String = File.createTempFile("ffxivdb-",".db").toPath.toString + def withRandomDatabase: Config = + config(Map("me.arcanis.ffxivbis.database.sqlite.db.url" -> s"jdbc:sqlite:$randomDatabasePath")) +} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala new file mode 100644 index 0000000..82115b6 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala @@ -0,0 +1,27 @@ +package me.arcanis.ffxivbis.service + +import akka.actor.ActorSystem +import akka.testkit.{ImplicitSender, TestKit} +import me.arcanis.ffxivbis.models.{Fixtures, Job} +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} + +import scala.concurrent.duration._ +import scala.language.postfixOps + +class AriyalaTest extends TestKit(ActorSystem("ariyala")) + with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + + private val timeout: FiniteDuration = 60 seconds + + override def afterAll: Unit = TestKit.shutdownActorSystem(system) + + "ariyala actor" must { + + "get best in slot set" in { + val ariyala = system.actorOf(Ariyala.props) + ariyala ! Ariyala.GetBiS(Fixtures.link, Job.DNC) + expectMsg(timeout, Fixtures.bis) + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/DatabaseBiSHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseBiSHandlerTest.scala new file mode 100644 index 0000000..b41cabf --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseBiSHandlerTest.scala @@ -0,0 +1,82 @@ +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.storage.Migration +import me.arcanis.ffxivbis.utils.Compare +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps + +class DatabaseBiSHandlerTest + extends TestKit(ActorSystem("database-bis-handler", Settings.withRandomDatabase)) + with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + + private val database = system.actorOf(impl.DatabaseImpl.props) + private val timeout: FiniteDuration = 60 seconds + + override def beforeAll: Unit = { + Await.result(Migration(system.settings.config), timeout) + Await.result((database ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) + } + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + Settings.clearDatabase(system.settings.config) + } + + "database bis handler" must { + + "add pieces to bis" in { + database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootBody) + expectMsg(timeout, 1) + + database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, Fixtures.lootHands) + expectMsg(timeout, 1) + } + + "get party bis set" in { + database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) => () + } + } + + "get bis set" in { + database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId)) + expectMsgPF(timeout) { + case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootBody, Fixtures.lootHands)) => () + } + } + + "remove piece from bis set" in { + database ! impl.DatabaseBiSHandler.RemovePieceFromBiS(Fixtures.playerEmpty.playerId, Fixtures.lootBody) + expectMsg(timeout, 1) + + database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyBiSCompare(party, Seq(Fixtures.lootHands)) => () + } + } + + "update piece in bis set" in { + val newPiece = Hands(isTome = false, Job.DNC) + + database ! impl.DatabaseBiSHandler.AddPieceToBis(Fixtures.playerEmpty.playerId, newPiece) + expectMsg(timeout, 1) + + database ! impl.DatabaseBiSHandler.GetBiS(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyBiSCompare(party, Seq(newPiece)) => () + } + } + + } + + private def partyBiSCompare[T](party: Seq[T], bis: Seq[Piece]): Boolean = + Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) => acc ++ player.asInstanceOf[Player].bis.pieces }, bis) +} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala new file mode 100644 index 0000000..8b834ae --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseLootHandlerTest.scala @@ -0,0 +1,86 @@ +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.storage.Migration +import me.arcanis.ffxivbis.utils.Compare +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps + +class DatabaseLootHandlerTest + extends TestKit(ActorSystem("database-loot-handler", Settings.withRandomDatabase)) + with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + + private val database = system.actorOf(impl.DatabaseImpl.props) + private val timeout: FiniteDuration = 60 seconds + + override def beforeAll: Unit = { + Await.result(Migration(system.settings.config), timeout) + Await.result((database ? impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty))(timeout).mapTo[Int], timeout) + } + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + Settings.clearDatabase(system.settings.config) + } + + "database loot handler actor" must { + + "add loot" in { + Fixtures.loot.foreach { piece => + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece) + expectMsg(timeout, 1) + } + } + + "get party loot" in { + database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyLootCompare(party, Fixtures.loot) => () + } + } + + "get loot" in { + database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, Some(Fixtures.playerEmpty.playerId)) + expectMsgPF(timeout) { + case party: Seq[_] if partyLootCompare(party, Fixtures.loot) => () + } + } + + "remove loot" in { + database ! impl.DatabaseLootHandler.RemovePieceFrom(Fixtures.playerEmpty.playerId, Fixtures.lootBody) + expectMsg(timeout, 1) + + val newLoot = Fixtures.loot.filterNot(_ == Fixtures.lootBody) + + database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyLootCompare(party, newLoot) => () + } + } + + "add same loot" in { + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, Fixtures.lootBody) + expectMsg(timeout, 1) + + Fixtures.loot.foreach { piece => + database ! impl.DatabaseLootHandler.AddPieceTo(Fixtures.playerEmpty.playerId, piece) + expectMsg(timeout, 1) + } + + database ! impl.DatabaseLootHandler.GetLoot(Fixtures.playerEmpty.partyId, None) + expectMsgPF(timeout) { + case party: Seq[_] if partyLootCompare(party, Fixtures.loot ++ Fixtures.loot) => () + } + } + + } + + private def partyLootCompare[T](party: Seq[T], loot: Seq[Piece]): Boolean = + Compare.seqEquals(party.foldLeft(Seq.empty[Piece]){ case (acc, player) => acc ++ player.asInstanceOf[Player].loot }, loot) +} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/DatabasePartyHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/DatabasePartyHandlerTest.scala new file mode 100644 index 0000000..75c5451 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/service/DatabasePartyHandlerTest.scala @@ -0,0 +1,73 @@ +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.storage.Migration +import me.arcanis.ffxivbis.utils.Compare +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps + +class DatabasePartyHandlerTest + extends TestKit(ActorSystem("database-party-handler", Settings.withRandomDatabase)) + with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + + private val database = system.actorOf(impl.DatabaseImpl.props) + private val timeout: FiniteDuration = 60 seconds + + override def beforeAll: Unit = { + Await.result(Migration(system.settings.config), timeout) + } + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + Settings.clearDatabase(system.settings.config) + } + + "database party handler actor" must { + + "add player" in { + database ! impl.DatabasePartyHandler.AddPlayer(Fixtures.playerEmpty) + expectMsg(timeout, 1) + } + + "get party" in { + database ! impl.DatabasePartyHandler.GetParty(Fixtures.partyId) + expectMsgPF(timeout) { + case p: Party if Compare.seqEquals(p.getPlayers, Seq(Fixtures.playerEmpty)) => () + } + } + + "get player" in { + database ! impl.DatabasePartyHandler.GetPlayer(Fixtures.playerEmpty.playerId) + expectMsg(timeout, Some(Fixtures.playerEmpty)) + } + + "update player" in { + val newPlayer = Fixtures.playerEmpty.copy(priority = 2) + + database ! impl.DatabasePartyHandler.AddPlayer(newPlayer) + expectMsg(timeout, 1) + + database ! impl.DatabasePartyHandler.GetPlayer(newPlayer.playerId) + expectMsg(timeout, Some(newPlayer)) + + database ! impl.DatabasePartyHandler.GetParty(Fixtures.partyId) + expectMsgPF(timeout) { + case p: Party if Compare.seqEquals(p.getPlayers, Seq(newPlayer)) => () + } + } + + "remove player" in { + database ! impl.DatabasePartyHandler.RemovePlayer(Fixtures.playerEmpty.playerId) + expectMsg(timeout, 1) + + database ! impl.DatabasePartyHandler.GetPlayer(Fixtures.playerEmpty.playerId) + expectMsg(timeout, None) + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/service/DatabaseUserHandlerTest.scala b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseUserHandlerTest.scala new file mode 100644 index 0000000..66a7469 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/service/DatabaseUserHandlerTest.scala @@ -0,0 +1,76 @@ +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.storage.Migration +import me.arcanis.ffxivbis.utils.Compare +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps + +class DatabaseUserHandlerTest + extends TestKit(ActorSystem("database-user-handler", Settings.withRandomDatabase)) + with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + + private val database = system.actorOf(impl.DatabaseImpl.props) + private val timeout: FiniteDuration = 60 seconds + + override def beforeAll: Unit = { + Await.result(Migration(system.settings.config), timeout) + } + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + Settings.clearDatabase(system.settings.config) + } + + "database user handler actor" must { + + "add user" in { + database ! impl.DatabaseUserHandler.AddUser(Fixtures.userAdmin, isHashedPassword = true) + expectMsg(timeout, 1) + } + + "get user" in { + database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, Fixtures.userAdmin.username) + expectMsg(timeout, Some(Fixtures.userAdmin)) + } + + "get users" in { + database ! impl.DatabaseUserHandler.AddUser(Fixtures.userGet, isHashedPassword = true) + expectMsg(timeout, 1) + + database ! impl.DatabaseUserHandler.GetUsers(Fixtures.partyId) + expectMsgPF(timeout) { + case u: Seq[_] if Compare.seqEquals(u, Fixtures.users) => () + } + } + + "update user" in { + val newUser= Fixtures.userGet.copy(password = Fixtures.userPassword2).withHashedPassword + val newUserSet = Seq(newUser, Fixtures.userAdmin) + + database ! impl.DatabaseUserHandler.AddUser(newUser, isHashedPassword = true) + expectMsg(timeout, 1) + + database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, newUser.username) + expectMsg(timeout, Some(newUser)) + + database ! impl.DatabaseUserHandler.GetUsers(Fixtures.partyId) + expectMsgPF(timeout) { + case u: Seq[_] if Compare.seqEquals(u, newUserSet) => () + } + } + + "remove user" in { + database ! impl.DatabaseUserHandler.DeleteUser(Fixtures.partyId, Fixtures.userGet.username) + expectMsg(timeout, 1) + + database ! impl.DatabaseUserHandler.GetUser(Fixtures.partyId, Fixtures.userGet.username) + expectMsg(timeout, None) + } + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala new file mode 100644 index 0000000..637aa00 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala @@ -0,0 +1,8 @@ +package me.arcanis.ffxivbis.utils + +object Compare { + def seqEquals[T](left: Seq[T], right: Seq[T]): Boolean = + left.groupBy(identity).view.mapValues(_.size).forall { + case (key, count) => right.count(_ == key) == count + } +} diff --git a/test.sbt b/test.sbt new file mode 100644 index 0000000..596bee4 --- /dev/null +++ b/test.sbt @@ -0,0 +1,4 @@ +libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.8" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" + +libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.5.26" % "test" diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..f861d76 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version := "0.9.0"