mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-04-24 17:27:17 +00:00
some tests
This commit is contained in:
parent
d1001ffb8e
commit
b228595a1b
23
build.sbt
23
build.sbt
@ -1,28 +1,5 @@
|
|||||||
name := "ffxivbis"
|
name := "ffxivbis"
|
||||||
|
|
||||||
version := "0.9.0"
|
|
||||||
|
|
||||||
scalaVersion := "2.13.1"
|
scalaVersion := "2.13.1"
|
||||||
|
|
||||||
scalacOptions ++= Seq("-deprecation", "-feature")
|
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"
|
|
||||||
|
|
||||||
|
19
libraries.sbt
Normal file
19
libraries.sbt
Normal file
@ -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"
|
@ -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);
|
@ -1,6 +1,6 @@
|
|||||||
create table players (
|
create table players (
|
||||||
party_id text not null,
|
party_id text not null,
|
||||||
player_id integer primary key,
|
player_id integer primary key autoincrement,
|
||||||
created integer not null,
|
created integer not null,
|
||||||
nick text not null,
|
nick text not null,
|
||||||
job 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 unique index players_nick_job_idx on players(party_id, nick, job);
|
||||||
|
|
||||||
create table loot (
|
create table loot (
|
||||||
loot_id integer primary key,
|
loot_id integer primary key autoincrement,
|
||||||
player_id integer not null,
|
player_id integer not null,
|
||||||
created integer not null,
|
created integer not null,
|
||||||
piece text 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 (
|
create table users (
|
||||||
party_id text not null,
|
party_id text not null,
|
||||||
user_id integer primary key,
|
user_id integer primary key autoincrement,
|
||||||
username text not null,
|
username text not null,
|
||||||
password text not null,
|
password text not null,
|
||||||
permission text not null);
|
permission text not null);
|
5
src/main/resources/logback.xml
Normal file
5
src/main/resources/logback.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||||
|
|
||||||
|
</configuration>
|
@ -22,6 +22,16 @@ me.arcanis.ffxivbis {
|
|||||||
}
|
}
|
||||||
numThreads = 10
|
numThreads = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postgresql {
|
||||||
|
profile = "slick.jdbc.PostgresProfile$"
|
||||||
|
db {
|
||||||
|
url = "jdbc:postgresql://localhost/ffxivbis"
|
||||||
|
user = "user"
|
||||||
|
password = "password"
|
||||||
|
}
|
||||||
|
numThreads = 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
@ -20,5 +20,5 @@ class AriyalaHelper(ariyala: ActorRef) {
|
|||||||
|
|
||||||
def downloadBiS(link: String, job: Job.Job)
|
def downloadBiS(link: String, job: Job.Job)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[BiS] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[BiS] =
|
||||||
(ariyala ? Ariyala.GetBiS(link, job)).mapTo[Seq[Piece]].map(BiS(_))
|
(ariyala ? Ariyala.GetBiS(link, job)).mapTo[BiS]
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a
|
|||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] =
|
||||||
maybePlayerId match {
|
maybePlayerId match {
|
||||||
case Some(playerId) =>
|
case Some(playerId) =>
|
||||||
(storage ? DatabasePartyHandler.GetPlayer(playerId)).mapTo[Player].map(Seq(_))
|
(storage ? DatabasePartyHandler.GetPlayer(playerId)).mapTo[Option[Player]].map(_.toSeq)
|
||||||
case None =>
|
case None =>
|
||||||
(storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq)
|
(storage ? DatabasePartyHandler.GetParty(partyId)).mapTo[Party].map(_.players.values.toSeq)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class UserHelper(storage: ActorRef) {
|
|||||||
|
|
||||||
def addUser(user: User, isHashedPassword: Boolean)
|
def addUser(user: User, isHashedPassword: Boolean)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
(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)
|
def user(partyId: String, username: String)
|
||||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Option[User]] =
|
||||||
|
@ -22,4 +22,5 @@ case class User(partyId: String,
|
|||||||
def hash: String = BCrypt.hashpw(password, BCrypt.gensalt)
|
def hash: String = BCrypt.hashpw(password, BCrypt.gensalt)
|
||||||
def verify(plain: String): Boolean = BCrypt.checkpw(plain, password)
|
def verify(plain: String): Boolean = BCrypt.checkpw(plain, password)
|
||||||
def verityScope(scope: Permission.Value): Boolean = permission >= scope
|
def verityScope(scope: Permission.Value): Boolean = permission >= scope
|
||||||
|
def withHashedPassword: User = copy(password = hash)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import akka.stream.ActorMaterializer
|
|||||||
import akka.stream.scaladsl.{Keep, Sink}
|
import akka.stream.scaladsl.{Keep, Sink}
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
import com.typesafe.scalalogging.StrictLogging
|
import com.typesafe.scalalogging.StrictLogging
|
||||||
import me.arcanis.ffxivbis.models.{Job, Piece}
|
import me.arcanis.ffxivbis.models.{BiS, Job, Piece}
|
||||||
import spray.json._
|
import spray.json._
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
@ -39,7 +39,7 @@ class Ariyala extends Actor with StrictLogging {
|
|||||||
override def receive: Receive = {
|
override def receive: Receive = {
|
||||||
case GetBiS(link, job) =>
|
case GetBiS(link, job) =>
|
||||||
val client = sender()
|
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]] = {
|
private def get(link: String, job: Job.Job): Future[Seq[Piece]] = {
|
||||||
|
@ -19,6 +19,11 @@ trait Database extends Actor with StrictLogging {
|
|||||||
implicit def executionContext: ExecutionContext
|
implicit def executionContext: ExecutionContext
|
||||||
def profile: DatabaseProfile
|
def profile: DatabaseProfile
|
||||||
|
|
||||||
|
override def postStop(): Unit = {
|
||||||
|
profile.db.close()
|
||||||
|
super.postStop()
|
||||||
|
}
|
||||||
|
|
||||||
def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] =
|
def filterParty(party: Party, maybePlayerId: Option[PlayerId]): Seq[Player] =
|
||||||
(party, maybePlayerId) match {
|
(party, maybePlayerId) match {
|
||||||
case (_, Some(playerId)) => party.player(playerId).map(Seq(_)).getOrElse(Seq.empty)
|
case (_, Some(playerId)) => party.player(playerId).map(Seq(_)).getOrElse(Seq.empty)
|
||||||
|
@ -12,6 +12,8 @@ import akka.pattern.pipe
|
|||||||
import me.arcanis.ffxivbis.models.{BiS, Player, PlayerId}
|
import me.arcanis.ffxivbis.models.{BiS, Player, PlayerId}
|
||||||
import me.arcanis.ffxivbis.service.Database
|
import me.arcanis.ffxivbis.service.Database
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
trait DatabasePartyHandler { this: Database =>
|
trait DatabasePartyHandler { this: Database =>
|
||||||
import DatabasePartyHandler._
|
import DatabasePartyHandler._
|
||||||
|
|
||||||
@ -26,11 +28,16 @@ trait DatabasePartyHandler { this: Database =>
|
|||||||
|
|
||||||
case GetPlayer(playerId) =>
|
case GetPlayer(playerId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
val player = for {
|
val player = profile.getPlayerFull(playerId).flatMap { maybePlayerData =>
|
||||||
bis <- profile.getPiecesBiS(playerId)
|
Future.traverse(maybePlayerData.toSeq) { playerData =>
|
||||||
loot <- profile.getPieces(playerId)
|
for {
|
||||||
} yield Player(playerId.partyId, playerId.job, playerId.nick,
|
bis <- profile.getPiecesBiS(playerId)
|
||||||
BiS(bis.map(_.piece)), loot.map(_.piece))
|
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)
|
player.pipeTo(client)
|
||||||
|
|
||||||
case RemovePlayer(playerId) =>
|
case RemovePlayer(playerId) =>
|
||||||
|
@ -16,6 +16,11 @@ trait DatabaseUserHandler { this: Database =>
|
|||||||
import DatabaseUserHandler._
|
import DatabaseUserHandler._
|
||||||
|
|
||||||
def userHandler: Receive = {
|
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) =>
|
case DeleteUser(partyId, username) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
profile.deleteUser(partyId, username).pipeTo(client)
|
profile.deleteUser(partyId, username).pipeTo(client)
|
||||||
@ -27,17 +32,12 @@ trait DatabaseUserHandler { this: Database =>
|
|||||||
case GetUsers(partyId) =>
|
case GetUsers(partyId) =>
|
||||||
val client = sender()
|
val client = sender()
|
||||||
profile.getUsers(partyId).pipeTo(client)
|
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 {
|
object DatabaseUserHandler {
|
||||||
|
case class AddUser(user: User, isHashedPassword: Boolean)
|
||||||
case class DeleteUser(partyId: String, username: String)
|
case class DeleteUser(partyId: String, username: String)
|
||||||
case class GetUser(partyId: String, username: String)
|
case class GetUser(partyId: String, username: String)
|
||||||
case class GetUsers(partyId: String)
|
case class GetUsers(partyId: String)
|
||||||
case class InsertUser(user: User, isHashedPassword: Boolean)
|
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,10 @@ trait LootProfile { this: DatabaseProfile =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
def deletePieceById(piece: Piece)(playerId: Long): Future[Int] =
|
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(playerId: Long): Future[Seq[Loot]] = getPiecesById(Seq(playerId))
|
||||||
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
def getPiecesById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
||||||
db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot))
|
db.run(piecesLoot(playerIds).result).map(_.map(_.toLoot))
|
||||||
|
@ -10,6 +10,7 @@ package me.arcanis.ffxivbis.storage
|
|||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import org.flywaydb.core.Flyway
|
import org.flywaydb.core.Flyway
|
||||||
|
import org.flywaydb.core.api.configuration.ClassicConfiguration
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
|
||||||
@ -21,7 +22,16 @@ class Migration(config: Config) {
|
|||||||
val username = section.getString("db.user")
|
val username = section.getString("db.user")
|
||||||
val password = section.getString("db.password")
|
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())
|
Future.successful(flyway.migrate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ trait PlayersProfile { this: DatabaseProfile =>
|
|||||||
})
|
})
|
||||||
def getPlayer(playerId: PlayerId): Future[Option[Long]] =
|
def getPlayer(playerId: PlayerId): Future[Option[Long]] =
|
||||||
db.run(player(playerId).map(_.playerId).result.headOption)
|
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]] =
|
def getPlayers(partyId: String): Future[Seq[Long]] =
|
||||||
db.run(players(partyId).map(_.playerId).result)
|
db.run(players(partyId).map(_.playerId).result)
|
||||||
def insertPlayer(playerObj: Player): Future[Int] =
|
def insertPlayer(playerObj: Player): Future[Int] =
|
||||||
|
@ -22,7 +22,7 @@ trait UsersProfile { this: DatabaseProfile =>
|
|||||||
}
|
}
|
||||||
object UserRep {
|
object UserRep {
|
||||||
def fromUser(user: User, id: Option[Long]): 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") {
|
class Users(tag: Tag) extends Table[UserRep](tag, "users") {
|
||||||
@ -47,8 +47,8 @@ trait UsersProfile { this: DatabaseProfile =>
|
|||||||
def getUsers(partyId: String): Future[Seq[User]] =
|
def getUsers(partyId: String): Future[Seq[User]] =
|
||||||
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
db.run(user(partyId, None).result).map(_.map(_.toUser))
|
||||||
def insertUser(userObj: User): Future[Int] =
|
def insertUser(userObj: User): Future[Int] =
|
||||||
db.run(user(userObj.partyId, Some(userObj.username)).result.headOption).map {
|
db.run(user(userObj.partyId, Some(userObj.username)).map(_.userId).result.headOption).map {
|
||||||
case Some(user) => db.run(usersTable.update(UserRep.fromUser(userObj, user.userId)))
|
case Some(id) => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, Some(id))))
|
||||||
case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None)))
|
case _ => db.run(usersTable.insertOrUpdate(UserRep.fromUser(userObj, None)))
|
||||||
}.flatten
|
}.flatten
|
||||||
|
|
||||||
|
43
src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala
Normal file
43
src/test/scala/me/arcanis/ffxivbis/models/Fixtures.scala
Normal file
@ -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)
|
||||||
|
}
|
29
src/test/scala/me/arcanis/ffxivbis/models/Settings.scala
Normal file
29
src/test/scala/me/arcanis/ffxivbis/models/Settings.scala
Normal file
@ -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"))
|
||||||
|
}
|
27
src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala
Normal file
27
src/test/scala/me/arcanis/ffxivbis/service/AriyalaTest.scala
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala
Normal file
8
src/test/scala/me/arcanis/ffxivbis/utils/Compare.scala
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
4
test.sbt
Normal file
4
test.sbt
Normal file
@ -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"
|
1
version.sbt
Normal file
1
version.sbt
Normal file
@ -0,0 +1 @@
|
|||||||
|
version := "0.9.0"
|
Loading…
Reference in New Issue
Block a user