9 Commits
0.9.4 ... 0.8.7

Author SHA1 Message Date
12c99bd52c fix to recent xivapi changes 2020-02-25 03:17:03 +03:00
bdfb5aedeb Update README.md 2020-02-12 01:56:16 +03:00
666a1b8b7a fix api urls in swagger 2019-12-29 03:47:43 +03:00
65a4a25b3a force type number 2019-11-22 01:40:52 +03:00
37c444a5b9 badges 2019-11-18 23:38:31 +03:00
f5a644747d change travis config 2019-11-18 23:24:50 +03:00
ab790e87ff better job handling 2019-11-18 23:22:23 +03:00
9faceb4f61 some fixes 2019-11-18 23:00:54 +03:00
65b9e53b66 additional methods to types endpoint 2019-11-17 16:23:43 +03:00
21 changed files with 169 additions and 40 deletions

View File

@ -1,9 +1,9 @@
sudo: required language: scala
language: generic scala:
- 2.13.1
services: sbt_args: -no-colors
- docker
script: script:
- docker run -it --rm -v "$(pwd):/opt/build" -w /opt/build mozilla/sbt sbt compile - sbt compile
- docker run -it --rm -v "$(pwd):/opt/build" -w /opt/build mozilla/sbt sbt test - sbt test

View File

@ -1,10 +1,12 @@
# FFXIV BiS # FFXIV BiS
[![Build Status](https://travis-ci.org/arcan1s/ffxivbis.svg?branch=master)](https://travis-ci.org/arcan1s/ffxivbis) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/arcan1s/ffxivbis)
Service which allows to manage savage loot distribution easy. Service which allows to manage savage loot distribution easy.
## Installation and usage ## Installation and usage
In general compliation process looks like: In general compilation process looks like:
```bash ```bash
sbt dist sbt dist
@ -26,4 +28,4 @@ REST API documentation is available at `http://0.0.0.0:8000/swagger`. HTML repre
## Public service ## Public service
There is also public service which is available at https://ffxivbis.arcanis.me. There is also public service which is available at http://ffxivbis.arcanis.me.

View File

@ -5,7 +5,7 @@ me.arcanis.ffxivbis {
# 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 = "abcdef" #xivapi-key = "abcdef"
} }
database { database {
@ -54,5 +54,22 @@ me.arcanis.ffxivbis {
host = "127.0.0.1" host = "127.0.0.1"
# port to bind, int, required # port to bind, int, required
port = 8000 port = 8000
# hostname to use in docs, if not set host:port will be used
#hostname = "127.0.0.1:8000"
# rate limits
limits {
intetval = 1m
max-count = 60
}
}
default-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
fixed-pool-size = 16
}
throughput = 1
} }
} }

View File

@ -3,7 +3,7 @@ REST json API description to interact with FFXIVBiS service.
# Basic workflow # Basic workflow
* Create party using `PUT /api/v1/party` endpoint. It consumes username and password of administrator (which can't be restored). As the result it returns unique id of created party. * Create party using `PUT /api/v1/party` endpoint. It consumes username and password of administrator (which can't be restored). As the result it returns unique id of created party.
* Create new users which have access to this party. Note that user is belong to specific party id and in scope of the specified party it must be unique. * Create new users which have access to this party. Note that user belongs to specific party id and in scope of the specified party it must be unique.
* Add players with their best in slot sets (probably by using ariyala links). * Add players with their best in slot sets (probably by using ariyala links).
* Add loot items if any. * Add loot items if any.
* By using `PUT /api/v1/party/{partyId}/loot` API find players which are better for the specified loot. * By using `PUT /api/v1/party/{partyId}/loot` API find players which are better for the specified loot.

View File

@ -27,8 +27,9 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
implicit val timeout: Timeout = implicit val timeout: Timeout =
config.getDuration("me.arcanis.ffxivbis.settings.request-timeout") config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")
private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala) private val rootApiV1Endpoint: RootApiV1Endpoint = new RootApiV1Endpoint(storage, ariyala, config)
private val rootView: RootView = new RootView(storage, ariyala) private val rootView: RootView = new RootView(storage, ariyala)
private val swagger: Swagger = new Swagger(config)
private val httpLogger = Logger("http") private val httpLogger = Logger("http")
private val withHttpLog: Directive0 = private val withHttpLog: Directive0 =
@ -43,7 +44,7 @@ class RootEndpoint(system: ActorSystem, storage: ActorRef, ariyala: ActorRef)
def route: Route = def route: Route =
withHttpLog { withHttpLog {
apiRoute ~ htmlRoute ~ Swagger.routes ~ swaggerUIRoute apiRoute ~ htmlRoute ~ swagger.routes ~ swaggerUIRoute
} }
private def apiRoute: Route = private def apiRoute: Route =

View File

@ -10,11 +10,12 @@ package me.arcanis.ffxivbis.http
import com.github.swagger.akka.SwaggerHttpService import com.github.swagger.akka.SwaggerHttpService
import com.github.swagger.akka.model.{Info, License} import com.github.swagger.akka.model.{Info, License}
import com.typesafe.config.Config
import io.swagger.v3.oas.models.security.SecurityScheme import io.swagger.v3.oas.models.security.SecurityScheme
import scala.io.Source import scala.io.Source
object Swagger extends SwaggerHttpService { class Swagger(config: Config) extends SwaggerHttpService {
override val apiClasses: Set[Class[_]] = Set( override val apiClasses: Set[Class[_]] = Set(
classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint], classOf[api.v1.BiSEndpoint], classOf[api.v1.LootEndpoint],
classOf[api.v1.PlayerEndpoint], classOf[api.v1.TypesEndpoint], classOf[api.v1.PlayerEndpoint], classOf[api.v1.TypesEndpoint],
@ -28,12 +29,16 @@ object Swagger extends SwaggerHttpService {
license = Some(License("BSD", "https://raw.githubusercontent.com/arcan1s/ffxivbis/master/LICENSE")) license = Some(License("BSD", "https://raw.githubusercontent.com/arcan1s/ffxivbis/master/LICENSE"))
) )
override val host: String =
if (config.hasPath("me.arcanis.ffxivbis.web.hostname")) config.getString("me.arcanis.ffxivbis.web.hostname")
else s"${config.getString("me.arcanis.ffxivbis.web.host")}:${config.getString("me.arcanis.ffxivbis.web.port")}"
private val basicAuth = new SecurityScheme() private val basicAuth = new SecurityScheme()
.description("basic http auth") .description("basic http auth")
.`type`(SecurityScheme.Type.HTTP) .`type`(SecurityScheme.Type.HTTP)
.in(SecurityScheme.In.HEADER) .in(SecurityScheme.In.HEADER)
.scheme("bearer") .scheme("bearer")
override def securitySchemes: Map[String, SecurityScheme] = Map("basic auth" -> basicAuth) override val securitySchemes: Map[String, SecurityScheme] = Map("basic auth" -> basicAuth)
override val unwantedDefinitions: Seq[String] = override val unwantedDefinitions: Seq[String] =
Seq("Function1", "Function1RequestContextFutureRouteResult") Seq("Function1", "Function1RequestContextFutureRouteResult")

View File

@ -18,6 +18,9 @@ import spray.json._
trait HttpHandler extends StrictLogging { this: JsonSupport => trait HttpHandler extends StrictLogging { this: JsonSupport =>
implicit def exceptionHandler: ExceptionHandler = ExceptionHandler { implicit def exceptionHandler: ExceptionHandler = ExceptionHandler {
case ex: IllegalArgumentException =>
complete(StatusCodes.BadRequest, ErrorResponse(ex.getMessage))
case other: Exception => case other: Exception =>
logger.error("exception during request completion", other) logger.error("exception during request completion", other)
complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error")) complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error"))

View File

@ -12,16 +12,17 @@ import akka.actor.ActorRef
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.config.Config
import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport import me.arcanis.ffxivbis.http.api.v1.json.JsonSupport
class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef) class RootApiV1Endpoint(storage: ActorRef, ariyala: ActorRef, config: Config)
(implicit timeout: Timeout) (implicit timeout: Timeout)
extends JsonSupport with HttpHandler { extends JsonSupport with HttpHandler {
private val biSEndpoint = new BiSEndpoint(storage, ariyala) private val biSEndpoint = new BiSEndpoint(storage, ariyala)
private val lootEndpoint = new LootEndpoint(storage) private val lootEndpoint = new LootEndpoint(storage)
private val playerEndpoint = new PlayerEndpoint(storage, ariyala) private val playerEndpoint = new PlayerEndpoint(storage, ariyala)
private val typesEndpoint = new TypesEndpoint private val typesEndpoint = new TypesEndpoint(config)
private val userEndpoint = new UserEndpoint(storage) private val userEndpoint = new UserEndpoint(storage)
def route: Route = def route: Route =

View File

@ -10,17 +10,18 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import com.typesafe.config.Config
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema} import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Operation
import javax.ws.rs._ import javax.ws.rs._
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.{Job, Permission, Piece} import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
@Path("api/v1") @Path("api/v1")
class TypesEndpoint extends JsonSupport { class TypesEndpoint(config: Config) extends JsonSupport {
def route: Route = getJobs ~ getPermissions ~ getPieces def route: Route = getJobs ~ getPermissions ~ getPieces ~ getPriority
@GET @GET
@Path("types/jobs") @Path("types/jobs")
@ -39,7 +40,7 @@ class TypesEndpoint extends JsonSupport {
def getJobs: Route = def getJobs: Route =
path("types" / "jobs") { path("types" / "jobs") {
get { get {
complete(Job.available.map(_.toString)) complete(Job.availableWithAnyJob.map(_.toString))
} }
} }
@ -84,4 +85,25 @@ class TypesEndpoint extends JsonSupport {
complete(Piece.available) complete(Piece.available)
} }
} }
@GET
@Path("types/priority")
@Produces(value = Array("application/json"))
@Operation(summary = "priority list", description = "Returns the current priority list",
responses = Array(
new ApiResponse(responseCode = "200", description = "Priority order",
content = Array(new Content(
array = new ArraySchema(schema = new Schema(implementation = classOf[String]))
))),
new ApiResponse(responseCode = "500", description = "Internal server error",
content = Array(new Content(schema = new Schema(implementation = classOf[ErrorResponse])))),
),
tags = Array("types"),
)
def getPriority: Route =
path("types" / "priority") {
get {
complete(Party.getRules(config))
}
}
} }

View File

@ -18,7 +18,7 @@ case class PlayerResponse(
@Schema(description = "pieces in best in slot") bis: Option[Seq[PieceResponse]], @Schema(description = "pieces in best in slot") bis: Option[Seq[PieceResponse]],
@Schema(description = "looted pieces") loot: Option[Seq[PieceResponse]], @Schema(description = "looted pieces") loot: Option[Seq[PieceResponse]],
@Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String], @Schema(description = "link to best in slot", example = "https://ffxiv.ariyala.com/19V5R") link: Option[String],
@Schema(description = "player loot priority") priority: Option[Int]) { @Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) {
def toPlayer: Player = def toPlayer: Player =
Player(partyId, Job.withName(job), nick, Player(partyId, Job.withName(job), nick,
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), loot.getOrElse(Seq.empty).map(_.toPiece), BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), loot.getOrElse(Seq.empty).map(_.toPiece),

View File

@ -14,7 +14,7 @@ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.Route
import akka.util.Timeout import akka.util.Timeout
import me.arcanis.ffxivbis.http.{Authorization, LootHelper} import me.arcanis.ffxivbis.http.{Authorization, LootHelper}
import me.arcanis.ffxivbis.models.{Piece, PlayerIdWithCounters} import me.arcanis.ffxivbis.models.{Job, Piece, PlayerIdWithCounters}
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
@ -43,9 +43,9 @@ class LootSuggestView(override val storage: ActorRef)(implicit timeout: Timeout)
extractExecutionContext { implicit executionContext => extractExecutionContext { implicit executionContext =>
authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ =>
post { post {
formFields("piece".as[String], "is_tome".as[String].?) { (piece, maybeTome) => formFields("piece".as[String], "job".as[String], "is_tome".as[String].?) { (piece, job, maybeTome) =>
import me.arcanis.ffxivbis.utils.Implicits._ import me.arcanis.ffxivbis.utils.Implicits._
val maybePiece = Try(Piece(piece, maybeTome)).toOption val maybePiece = Try(Piece(piece, maybeTome, Job.withName(job))).toOption
onComplete(suggestLootCall(partyId, maybePiece)) { onComplete(suggestLootCall(partyId, maybePiece)) {
case Success(players) => case Success(players) =>
@ -90,6 +90,8 @@ object LootSuggestView {
form(action:=s"/party/$partyId/suggest", method:="post")( form(action:=s"/party/$partyId/suggest", method:="post")(
select(name:="piece", id:="piece", title:="piece") select(name:="piece", id:="piece", title:="piece")
(for (piece <- Piece.available) yield option(piece)), (for (piece <- Piece.available) yield option(piece)),
select(name:="job", id:="job", title:="job")
(for (job <- Job.availableWithAnyJob) yield option(job.toString)),
input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"), input(name:="is_tome", id:="is_tome", title:="is tome", `type`:="checkbox"),
label(`for`:="is_tome")("is tome gear"), label(`for`:="is_tome")("is tome gear"),
input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest") input(name:="suggest", id:="suggest", `type`:="submit", value:="suggest")

View File

@ -113,7 +113,7 @@ object LootView {
th("is tome"), th("is tome"),
th("") th("")
), ),
for (player <- party; piece <- player.bis.pieces) yield tr( for (player <- party; piece <- player.loot) yield tr(
td(`class`:="include_search")(player.playerId.toString), td(`class`:="include_search")(player.playerId.toString),
td(`class`:="include_search")(piece.piece), td(`class`:="include_search")(piece.piece),
td(piece.isTomeToString), td(piece.isTomeToString),

View File

@ -98,6 +98,12 @@ object Job {
lazy val available: Seq[Job] = lazy val available: Seq[Job] =
Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM) Seq(PLD, WAR, DRK, GNB, WHM, SCH, AST, MNK, DRG, NIN, SAM, BRD, MCH, DNC, BLM, SMN, RDM)
lazy val availableWithAnyJob: Seq[Job] = available.prepended(AnyJob)
def withName(job: String): Job.Job = available.find(_.toString == job.toUpperCase).getOrElse(AnyJob) def withName(job: String): Job.Job =
availableWithAnyJob.find(_.toString.equalsIgnoreCase(job.toUpperCase)) match {
case Some(value) => value
case None if job.isEmpty => AnyJob
case _ => throw new IllegalArgumentException("Invalid or unknown job")
}
} }

View File

@ -36,9 +36,6 @@ case class Party(partyId: String, rules: Seq[String], players: Map[PlayerId, Pla
} }
object Party { object Party {
private def getRules(config: Config): Seq[String] =
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
def apply(partyId: Option[String], config: Config): Party = def apply(partyId: Option[String], config: Config): Party =
new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty) new Party(partyId.getOrElse(randomPartyId), getRules(config), Map.empty)
@ -55,5 +52,8 @@ object Party {
Party(partyId, getRules(config), playersWithItems) Party(partyId, getRules(config), playersWithItems)
} }
def getRules(config: Config): Seq[String] =
config.getStringList("me.arcanis.ffxivbis.settings.priority").asScala.toSeq
def randomPartyId: String = Random.alphanumeric.take(20).mkString def randomPartyId: String = Random.alphanumeric.take(20).mkString
} }

View File

@ -34,7 +34,8 @@ class Ariyala extends Actor with StrictLogging {
private val http = Http()(context.system) private val http = Http()(context.system)
implicit private val materializer: ActorMaterializer = ActorMaterializer() implicit private val materializer: ActorMaterializer = ActorMaterializer()
implicit private val executionContext: ExecutionContext = context.dispatcher implicit private val executionContext: ExecutionContext =
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
override def receive: Receive = { override def receive: Receive = {
case GetBiS(link, job) => case GetBiS(link, job) =>
@ -42,6 +43,11 @@ class Ariyala extends Actor with StrictLogging {
get(link, job).map(BiS(_)).pipeTo(client) get(link, job).map(BiS(_)).pipeTo(client)
} }
override def postStop(): Unit = {
http.shutdownAllConnectionPools()
super.postStop()
}
private def get(link: String, job: Job.Job): Future[Seq[Piece]] = { private def get(link: String, job: Job.Job): Future[Seq[Piece]] = {
val id = Paths.get(link).normalize.getFileName.toString val id = Paths.get(link).normalize.getFileName.toString
val uri = Uri(ariyalaUrl) val uri = Uri(ariyalaUrl)
@ -55,7 +61,7 @@ class Ariyala extends Actor with StrictLogging {
val uri = Uri(xivapiUrl) val uri = Uri(xivapiUrl)
.withPath(Uri.Path / "item") .withPath(Uri.Path / "item")
.withQuery(Uri.Query(Map( .withQuery(Uri.Query(Map(
"columns" -> Seq("ID", "IsEquippable").mkString(","), "columns" -> Seq("ID", "Lot").mkString(","),
"ids" -> itemIds.mkString(","), "ids" -> itemIds.mkString(","),
"private_key" -> xivapiKey.getOrElse("") "private_key" -> xivapiKey.getOrElse("")
))) )))
@ -117,7 +123,7 @@ object Ariyala {
Future { Future {
js.fields("Results") match { js.fields("Results") match {
case array: JsArray => case array: JsArray =>
array.elements.map(_.asJsObject.getFields("ID", "IsEquippable") match { array.elements.map(_.asJsObject.getFields("ID", "Lot") match {
case Seq(JsNumber(id), JsNumber(isTome)) => id.toLong -> (isTome == 0) case Seq(JsNumber(id), JsNumber(isTome)) => id.toLong -> (isTome == 0)
case other => throw deserializationError(s"Could not parse $other") case other => throw deserializationError(s"Could not parse $other")
}).toMap }).toMap

View File

@ -23,7 +23,8 @@ class PartyService(storage: ActorRef) extends Actor with StrictLogging {
private val cacheTimeout: FiniteDuration = private val cacheTimeout: FiniteDuration =
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout") context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.cache-timeout")
implicit private val executionContext: ExecutionContext = context.dispatcher implicit private val executionContext: ExecutionContext =
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
implicit private val timeout: Timeout = implicit private val timeout: Timeout =
context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout") context.system.settings.config.getDuration("me.arcanis.ffxivbis.settings.request-timeout")

View File

@ -0,0 +1,47 @@
package me.arcanis.ffxivbis.service
import java.time.Instant
import akka.actor.Actor
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
class RateLimiter extends Actor {
import RateLimiter._
import me.arcanis.ffxivbis.utils.Implicits._
implicit private val executionContext: ExecutionContext = context.system.dispatcher
private val maxRequestCount: Int = context.system.settings.config.getInt("me.arcanis.ffxivbis.web.limits.max-count")
private val requestInterval: FiniteDuration = context.system.settings.config.getDuration("me.arcanis.ffxivbis.web.limits.interval")
override def receive: Receive = handle(Map.empty)
private def handle(cache: Map[String, Usage]): Receive = {
case username: String =>
val client = sender()
val usage = if (cache.contains(username)) {
cache(username)
} else {
context.system.scheduler.scheduleOnce(requestInterval, self, Reset(username))
Usage()
}
context become handle(cache + (username -> usage.increment))
val response = if (usage.count > maxRequestCount) Some(usage.left) else None
client ! response
case Reset(username) =>
context become handle(cache - username)
}
}
object RateLimiter {
private case class Usage(count: Int = 0, since: Instant = Instant.now) {
def increment: Usage = copy(count = count + 1)
def left: Long = (Instant.now.toEpochMilli - since.toEpochMilli) / 1000
}
case class Reset(username: String)
}

View File

@ -18,7 +18,8 @@ class DatabaseImpl extends Database
with DatabaseBiSHandler with DatabaseLootHandler with DatabaseBiSHandler with DatabaseLootHandler
with DatabasePartyHandler with DatabaseUserHandler { with DatabasePartyHandler with DatabaseUserHandler {
implicit val executionContext: ExecutionContext = context.dispatcher implicit val executionContext: ExecutionContext =
context.system.dispatchers.lookup("me.arcanis.ffxivbis.default-dispatcher")
val profile = new DatabaseProfile(executionContext, context.system.settings.config) val profile = new DatabaseProfile(executionContext, context.system.settings.config)
override def receive: Receive = override def receive: Receive =

View File

@ -3,8 +3,10 @@ package me.arcanis.ffxivbis.http.api.v1
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.ScalatestRouteTest import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.server._ import akka.http.scaladsl.server._
import com.typesafe.config.Config
import me.arcanis.ffxivbis.Settings
import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.http.api.v1.json._
import me.arcanis.ffxivbis.models.{Job, Permission, Piece} import me.arcanis.ffxivbis.models.{Job, Party, Permission, Piece}
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scala.language.postfixOps import scala.language.postfixOps
@ -12,14 +14,16 @@ import scala.language.postfixOps
class TypesEndpointTest extends WordSpec class TypesEndpointTest extends WordSpec
with Matchers with ScalatestRouteTest with JsonSupport { with Matchers with ScalatestRouteTest with JsonSupport {
private val route: Route = new TypesEndpoint().route private val route: Route = new TypesEndpoint(testConfig).route
override def testConfig: Config = Settings.withRandomDatabase
"api v1 types endpoint" must { "api v1 types endpoint" must {
"return all available jobs" in { "return all available jobs" in {
Get("/types/jobs") ~> route ~> check { Get("/types/jobs") ~> route ~> check {
status shouldEqual StatusCodes.OK status shouldEqual StatusCodes.OK
responseAs[Seq[String]] shouldEqual Job.available.map(_.toString) responseAs[Seq[String]] shouldEqual Job.availableWithAnyJob.map(_.toString)
} }
} }
@ -37,5 +41,12 @@ class TypesEndpointTest extends WordSpec
} }
} }
"return current priority" in {
Get("/types/priority") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[Seq[String]] shouldEqual Party.getRules(testConfig)
}
}
} }
} }

View File

@ -12,8 +12,12 @@ class JobTest extends WordSpecLike with Matchers with BeforeAndAfterAll {
} }
} }
"return AnyJob on unknown job" in { "return AnyJob" in {
Job.withName("random string") shouldEqual Job.AnyJob Job.withName("anyjob") shouldEqual Job.AnyJob
}
"fail on unknown job" in {
an [IllegalArgumentException] should be thrownBy Job.withName("random string")
} }
"equal AnyJob to others" in { "equal AnyJob to others" in {

View File

@ -1 +1 @@
version := "0.9.4" version := "0.9.6"