diff --git a/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala b/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala index 971b869..156de64 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/Authorization.scala @@ -37,23 +37,16 @@ trait Authorization { def authAdmin(partyId: String)(username: String, password: String)(implicit executionContext: ExecutionContext ): Future[Option[User]] = - authenticator(Permission.admin, partyId)(username, password) + auth.authenticator(Permission.admin, partyId)(username, password) def authGet(partyId: String)(username: String, password: String)(implicit executionContext: ExecutionContext ): Future[Option[User]] = - authenticator(Permission.get, partyId)(username, password) + auth.authenticator(Permission.get, partyId)(username, password) def authPost(partyId: String)(username: String, password: String)(implicit executionContext: ExecutionContext ): Future[Option[User]] = - authenticator(Permission.post, partyId)(username, password) + auth.authenticator(Permission.post, partyId)(username, password) - private def authenticator(scope: Permission.Value, partyId: String)(username: String, password: String)(implicit - executionContext: ExecutionContext - ): Future[Option[User]] = - auth.get(partyId, username).map { - case Some(user) if user.verify(password) && user.verityScope(scope) => Some(user) - case _ => None - } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala index 46cf1ff..384bc4c 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/AuthorizationProvider.scala @@ -14,19 +14,31 @@ import akka.util.Timeout import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} import com.typesafe.config.Config import me.arcanis.ffxivbis.messages.{GetUser, Message} -import me.arcanis.ffxivbis.models.User +import me.arcanis.ffxivbis.models.{Permission, User} import java.util.concurrent.TimeUnit -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} trait AuthorizationProvider { def get(partyId: String, username: String): Future[Option[User]] + + def authenticator[T](scope: Permission.Value, partyId: String)(username: String, password: String)(implicit + executionContext: ExecutionContext, + extractor: User => T + ): Future[Option[T]] = + get(partyId, username).map { + case Some(user) if user.verify(password) && user.verityScope(scope) => Some(extractor(user)) + case _ => None + } } object AuthorizationProvider { - def apply(config: Config, storage: ActorRef[Message], timeout: Timeout, scheduler: Scheduler): AuthorizationProvider = + def apply(config: Config, storage: ActorRef[Message])(implicit + timeout: Timeout, + scheduler: Scheduler + ): AuthorizationProvider = new AuthorizationProvider { private val cacheSize = config.getInt("me.arcanis.ffxivbis.web.authorization-cache.cache-size") private val cacheTimeout = diff --git a/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala index d995e14..e7202d2 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/RootEndpoint.scala @@ -28,7 +28,7 @@ class RootEndpoint(system: ActorSystem[Nothing], storage: ActorRef[Message], pro implicit val scheduler: Scheduler = system.scheduler implicit val timeout: Timeout = config.getTimeout("me.arcanis.ffxivbis.settings.request-timeout") - private val auth = AuthorizationProvider(config, storage, timeout, scheduler) + private val auth = AuthorizationProvider(config, storage) private val rootApiV1Endpoint = new RootApiV1Endpoint(storage, auth, provider, config) private val rootView = new RootView(auth) diff --git a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala index 9041ffe..3e69445 100644 --- a/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala +++ b/src/main/scala/me/arcanis/ffxivbis/messages/DatabaseMessage.scala @@ -8,36 +8,38 @@ */ package me.arcanis.ffxivbis.messages -import akka.actor.typed.{ActorRef, Behavior} +import akka.actor.typed.ActorRef import me.arcanis.ffxivbis.models._ import me.arcanis.ffxivbis.service.LootSelector sealed trait DatabaseMessage extends Message { def partyId: String -} -object DatabaseMessage { - - type Handler = PartialFunction[DatabaseMessage, Behavior[DatabaseMessage]] + def isReadOnly: Boolean } // bis handler trait BisDatabaseMessage extends DatabaseMessage case class AddPieceToBis(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } case class GetBiS(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) - extends BisDatabaseMessage + extends BisDatabaseMessage { + override val isReadOnly: Boolean = true +} case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } case class RemovePiecesFromBiS(playerId: PlayerId, replyTo: ActorRef[Unit]) extends BisDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } // loot handler @@ -45,54 +47,77 @@ trait LootDatabaseMessage extends DatabaseMessage case class AddPieceTo(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) extends LootDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } case class GetLoot(partyId: String, playerId: Option[PlayerId], replyTo: ActorRef[Seq[Player]]) - extends LootDatabaseMessage + extends LootDatabaseMessage { + override val isReadOnly: Boolean = true +} case class RemovePieceFrom(playerId: PlayerId, piece: Piece, isFreeLoot: Boolean, replyTo: ActorRef[Unit]) extends LootDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } case class SuggestLoot(partyId: String, piece: Piece, replyTo: ActorRef[LootSelector.LootSelectorResult]) - extends LootDatabaseMessage + extends LootDatabaseMessage { + override val isReadOnly: Boolean = true +} // party handler trait PartyDatabaseMessage extends DatabaseMessage case class AddPlayer(player: Player, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override def partyId: String = player.partyId + override val partyId: String = player.partyId + override val isReadOnly: Boolean = false } -case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends PartyDatabaseMessage +case class GetParty(partyId: String, replyTo: ActorRef[Party]) extends PartyDatabaseMessage { + override val isReadOnly: Boolean = true +} -case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends PartyDatabaseMessage +case class GetPartyDescription(partyId: String, replyTo: ActorRef[PartyDescription]) extends PartyDatabaseMessage { + override val isReadOnly: Boolean = true +} case class GetPlayer(playerId: PlayerId, replyTo: ActorRef[Option[Player]]) extends PartyDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = true } case class RemovePlayer(playerId: PlayerId, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override def partyId: String = playerId.partyId + override val partyId: String = playerId.partyId + override val isReadOnly: Boolean = false } case class UpdateParty(partyDescription: PartyDescription, replyTo: ActorRef[Unit]) extends PartyDatabaseMessage { - override def partyId: String = partyDescription.partyId + override val partyId: String = partyDescription.partyId + override val isReadOnly: Boolean = false } // user handler trait UserDatabaseMessage extends DatabaseMessage case class AddUser(user: User, isHashedPassword: Boolean, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { - override def partyId: String = user.partyId + override val partyId: String = user.partyId + override val isReadOnly: Boolean = false } -case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends UserDatabaseMessage +case class DeleteUser(partyId: String, username: String, replyTo: ActorRef[Unit]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true +} -case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends UserDatabaseMessage +case class Exists(partyId: String, replyTo: ActorRef[Boolean]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true +} -case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends UserDatabaseMessage +case class GetUser(partyId: String, username: String, replyTo: ActorRef[Option[User]]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true +} -case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends UserDatabaseMessage +case class GetUsers(partyId: String, replyTo: ActorRef[Seq[User]]) extends UserDatabaseMessage { + override val isReadOnly: Boolean = true +} diff --git a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala index 13161b9..d7eb595 100644 --- a/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala +++ b/src/main/scala/me/arcanis/ffxivbis/service/PartyService.scala @@ -16,7 +16,6 @@ import com.typesafe.scalalogging.StrictLogging import me.arcanis.ffxivbis.messages._ import me.arcanis.ffxivbis.models.Party -import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future} class PartyService(context: ActorContext[Message], storage: ActorRef[DatabaseMessage]) @@ -62,7 +61,8 @@ class PartyService(context: ActorContext[Message], storage: ActorRef[DatabaseMes case req: DatabaseMessage => storage ! req - Behaviors.receiveMessage(handle(cache - req.partyId)) + val result = if (req.isReadOnly) cache else cache - req.partyId + Behaviors.receiveMessage(handle(result)) } private def getPartyId: Future[String] = { diff --git a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala index 0ae0cad..855bb84 100644 --- a/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala +++ b/src/test/scala/me/arcanis/ffxivbis/Fixtures.scala @@ -84,4 +84,5 @@ object Fixtures { lazy val users: Seq[User] = Seq(userAdmin, userGet) lazy val authProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(Some(userAdmin)) + lazy val rejectingProvider: AuthorizationProvider = (_: String, _: String) => Future.successful(None) } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala new file mode 100644 index 0000000..ca424a8 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/AuthorizationTest.scala @@ -0,0 +1,44 @@ +package me.arcanis.ffxivbis.http + +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} +import akka.http.scaladsl.server.Route +import akka.http.scaladsl.testkit.ScalatestRouteTest +import me.arcanis.ffxivbis.Fixtures +import me.arcanis.ffxivbis.http.view.RootView +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class AuthorizationTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest { + + private val auth = + Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) + + "authorization directive" must { + + "accept credentials" in { + val route = new RootView(Fixtures.authProvider).routes + + Get(Uri(s"/party/${Fixtures.partyId}")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + + "reject credentials" in { + val route = new RootView(Fixtures.rejectingProvider).routes + + Get(Uri(s"/party/${Fixtures.partyId}")).withHeaders(auth) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.Unauthorized + } + } + + "reject with empty credentials" in { + val route = new RootView(Fixtures.authProvider).routes + + Get(Uri(s"/party/${Fixtures.partyId}")) ~> Route.seal(route) ~> check { + status shouldEqual StatusCodes.Unauthorized + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/HttpLogTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/HttpLogTest.scala new file mode 100644 index 0000000..0be6153 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/HttpLogTest.scala @@ -0,0 +1,22 @@ +package me.arcanis.ffxivbis.http + +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.server.Directives.complete +import akka.http.scaladsl.testkit.ScalatestRouteTest +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class HttpLogTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest { + + private val log = new HttpLog {} + + "log directive" must { + + "work with empty request" in { + Get(Uri("/")) ~> log.withHttpLog(complete(StatusCodes.OK)) ~> check { + status shouldEqual StatusCodes.OK + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/RootEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/RootEndpointTest.scala new file mode 100644 index 0000000..fbd47ce --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/RootEndpointTest.scala @@ -0,0 +1,39 @@ +package me.arcanis.ffxivbis.http + +import akka.actor.testkit.typed.scaladsl.ActorTestKit +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.testkit.ScalatestRouteTest +import com.typesafe.config.Config +import me.arcanis.ffxivbis.Settings +import me.arcanis.ffxivbis.service.PartyService +import me.arcanis.ffxivbis.service.bis.BisProvider +import me.arcanis.ffxivbis.service.database.Database +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class RootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest { + + private val testKit = ActorTestKit(Settings.withRandomDatabase) + override val testConfig: Config = testKit.system.settings.config + + private val storage = testKit.spawn(Database()) + private val provider = testKit.spawn(BisProvider()) + private val party = testKit.spawn(PartyService(storage)) + private val route = new RootEndpoint(testKit.system, party, provider).routes + + "root route" must { + + "return swagger ui" in { + Get(Uri("/api-docs")) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + + "return static routes" in { + Get(Uri("/static/favicon.ico")) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/SwaggerTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/SwaggerTest.scala new file mode 100644 index 0000000..3b7347e --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/SwaggerTest.scala @@ -0,0 +1,28 @@ +package me.arcanis.ffxivbis.http + +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.testkit.ScalatestRouteTest +import me.arcanis.ffxivbis.Settings +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class SwaggerTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest { + + private val swagger = new Swagger(Settings.withRandomDatabase) + + "swagger guard" must { + + "generate json" in { + Get(Uri("/api-docs/swagger.json")) ~> swagger.routes ~> check { + status shouldEqual StatusCodes.OK + } + } + + "generate yml" in { + Get(Uri("/api-docs/swagger.yaml")) ~> swagger.routes ~> check { + status shouldEqual StatusCodes.OK + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala index f588ac5..8957e68 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpointTest.scala @@ -95,5 +95,15 @@ class LootEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRoute } } + "suggest loot" in { + val entity = PieceModel.fromPiece(Fixtures.lootBody) + val response = Seq(Fixtures.playerEmpty.withCounters(Some(Fixtures.lootBody))).map(PlayerIdWithCountersModel.fromPlayerId) + + Put(endpoint, entity).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[PlayerIdWithCountersModel]] shouldEqual response + } + } + } } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala index 50db486..4d47504 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpointTest.scala @@ -7,6 +7,7 @@ import akka.http.scaladsl.model.{StatusCodes, Uri} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestKit import com.typesafe.config.Config +import me.arcanis.ffxivbis import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.messages.{AddPlayer, AddUser} import me.arcanis.ffxivbis.service.PartyService @@ -52,7 +53,7 @@ class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRou "api v1 player endpoint" must { - "get users" in { + "get users belonging to the party" in { val response = Seq(PlayerModel.fromPlayer(Fixtures.playerEmpty)) Get(endpoint).withHeaders(auth) ~> route ~> check { @@ -61,5 +62,42 @@ class PlayerEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRou } } + "get party stats" in { + val response = Seq(PlayerIdWithCountersModel.fromPlayerId(Fixtures.playerEmpty.withCounters(None))) + + Get(endpoint.withPath(endpoint.path / "stats")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[PlayerIdWithCountersModel]] shouldEqual response + } + } + + "add new player to the party" in { + val entity = PlayerActionModel(ApiAction.add, PlayerModel.fromPlayer(Fixtures.playerWithBiS)) + + Post(endpoint, entity).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.Accepted + responseAs[String] shouldEqual "" + } + + Get(endpoint).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[PlayerModel]].map(_.toPlayer.playerId) should contain(Fixtures.playerWithBiS.playerId) + } + } + + "remove player from the party" in { + val entity = PlayerActionModel(ApiAction.remove, PlayerModel.fromPlayer(Fixtures.playerEmpty)) + + Post(endpoint, entity).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.Accepted + responseAs[String] shouldEqual "" + } + + Get(endpoint).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[Seq[PlayerModel]].map(_.toPlayer.playerId) should not contain(Fixtures.playerEmpty.playerId) + } + } + } } diff --git a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/StatusEndpointTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/StatusEndpointTest.scala index c50f593..9de0010 100644 --- a/src/test/scala/me/arcanis/ffxivbis/http/api/v1/StatusEndpointTest.scala +++ b/src/test/scala/me/arcanis/ffxivbis/http/api/v1/StatusEndpointTest.scala @@ -2,8 +2,6 @@ package me.arcanis.ffxivbis.http.api.v1 import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import com.typesafe.config.Config -import me.arcanis.ffxivbis.Settings import me.arcanis.ffxivbis.http.api.v1.json._ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike @@ -13,8 +11,6 @@ import scala.language.postfixOps class StatusEndpointTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest with JsonSupport { - override val testConfig: Config = Settings.withRandomDatabase - private val route = new StatusEndpoint().routes "api v1 status endpoint" must { diff --git a/src/test/scala/me/arcanis/ffxivbis/http/view/RootViewTest.scala b/src/test/scala/me/arcanis/ffxivbis/http/view/RootViewTest.scala new file mode 100644 index 0000000..a073fd0 --- /dev/null +++ b/src/test/scala/me/arcanis/ffxivbis/http/view/RootViewTest.scala @@ -0,0 +1,55 @@ +package me.arcanis.ffxivbis.http.view + +import akka.http.scaladsl.model.{StatusCodes, Uri} +import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials} +import akka.http.scaladsl.testkit.ScalatestRouteTest +import me.arcanis.ffxivbis.Fixtures +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +class RootViewTest extends AnyWordSpecLike with Matchers with ScalatestRouteTest { + + private val auth = + Authorization(BasicHttpCredentials(Fixtures.userAdmin.username, Fixtures.userPassword)) + + private val route = new RootView(Fixtures.authProvider).routes + + "html view endpoint" must { + + "return root view" in { + Get(Uri("/")) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[String] should not be empty + } + } + + "return root party view" in { + Get(Uri(s"/party/${Fixtures.partyId}")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[String] should not be empty + } + } + + "return bis view" in { + Get(Uri(s"/party/${Fixtures.partyId}/bis")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[String] should not be empty + } + } + + "return loot view" in { + Get(Uri(s"/party/${Fixtures.partyId}/loot")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[String] should not be empty + } + } + + "return users view" in { + Get(Uri(s"/party/${Fixtures.partyId}/users")).withHeaders(auth) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[String] should not be empty + } + } + + } +} diff --git a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala index 41ede69..650e540 100644 --- a/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala +++ b/src/test/scala/me/arcanis/ffxivbis/utils/Converters.scala @@ -6,5 +6,7 @@ import java.time.Instant import scala.language.implicitConversions object Converters { + implicit def pieceToLoot(piece: Piece): Loot = Loot(-1, piece, Instant.ofEpochMilli(0), isFreeLoot = false) + }