diff options
author | Jakob Odersky <jakob@driver.xyz> | 2018-09-12 16:40:57 -0700 |
---|---|---|
committer | Jakob Odersky <jakob@odersky.com> | 2018-10-09 16:19:39 -0700 |
commit | 4d1197099ce4e721c18bf4cacbb2e1980e4210b5 (patch) | |
tree | 1d3c2e482d5acf8ca613ba65fb5401c41ad686b5 /src/test/scala/xyz/driver/core/rest | |
parent | 7c755c77afbd67ae2ded9d8b004736d4e27e208f (diff) | |
download | driver-core-4d1197099ce4e721c18bf4cacbb2e1980e4210b5.tar.gz driver-core-4d1197099ce4e721c18bf4cacbb2e1980e4210b5.tar.bz2 driver-core-4d1197099ce4e721c18bf4cacbb2e1980e4210b5.zip |
Move REST functionality to separate project
Diffstat (limited to 'src/test/scala/xyz/driver/core/rest')
4 files changed, 0 insertions, 462 deletions
diff --git a/src/test/scala/xyz/driver/core/rest/DriverAppTest.scala b/src/test/scala/xyz/driver/core/rest/DriverAppTest.scala deleted file mode 100644 index 324c8d8..0000000 --- a/src/test/scala/xyz/driver/core/rest/DriverAppTest.scala +++ /dev/null @@ -1,89 +0,0 @@ -package xyz.driver.core.rest - -import akka.http.scaladsl.model.headers._ -import akka.http.scaladsl.model.{HttpMethod, StatusCodes} -import akka.http.scaladsl.server.{Directives, Route} -import akka.http.scaladsl.testkit.ScalatestRouteTest -import com.typesafe.config.ConfigFactory -import org.scalatest.{AsyncFlatSpec, Matchers} -import xyz.driver.core.app.{DriverApp, SimpleModule} - -class DriverAppTest extends AsyncFlatSpec with ScalatestRouteTest with Matchers with Directives { - val config = ConfigFactory.parseString(""" - |application { - | cors { - | allowedOrigins: ["example.com"] - | } - |} - """.stripMargin).withFallback(ConfigFactory.load) - - val origin = Origin(HttpOrigin("https", Host("example.com"))) - val allowedOrigins = Set(HttpOrigin("https", Host("example.com"))) - val allowedMethods: collection.immutable.Seq[HttpMethod] = { - import akka.http.scaladsl.model.HttpMethods._ - collection.immutable.Seq(GET, PUT, POST, PATCH, DELETE, OPTIONS, TRACE) - } - - import scala.reflect.runtime.universe.typeOf - class TestApp(testRoute: Route) - extends DriverApp( - appName = "test-app", - version = "0.0.1", - gitHash = "deadb33f", - modules = Seq(new SimpleModule("test-module", theRoute = testRoute, routeType = typeOf[DriverApp])), - config = config, - log = xyz.driver.core.logging.NoLogger - ) - - it should "respond with the correct CORS headers for the swagger OPTIONS route" in { - val route = new TestApp(get(complete(StatusCodes.OK))) - Options(s"/api-docs/swagger.json").withHeaders(origin) ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - headers should contain(`Access-Control-Allow-Origin`(HttpOriginRange(allowedOrigins.toSeq: _*))) - header[`Access-Control-Allow-Methods`].get.methods should contain theSameElementsAs allowedMethods - } - } - - it should "respond with the correct CORS headers for the test route" in { - val route = new TestApp(get(complete(StatusCodes.OK))) - Get(s"/api/v1/test").withHeaders(origin) ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - headers should contain(`Access-Control-Allow-Origin`(HttpOriginRange(allowedOrigins.toSeq: _*))) - } - } - - it should "respond with the correct CORS headers for a concatenated route" in { - val route = new TestApp(get(complete(StatusCodes.OK)) ~ post(complete(StatusCodes.OK))) - Post(s"/api/v1/test").withHeaders(origin) ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - headers should contain(`Access-Control-Allow-Origin`(HttpOriginRange(allowedOrigins.toSeq: _*))) - } - } - - it should "allow subdomains of allowed origin suffixes" in { - val route = new TestApp(get(complete(StatusCodes.OK))) - Get(s"/api/v1/test") - .withHeaders(Origin(HttpOrigin("https", Host("foo.example.com")))) ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - headers should contain(`Access-Control-Allow-Origin`(HttpOrigin("https", Host("foo.example.com")))) - } - } - - it should "respond with default domains for invalid origins" in { - val route = new TestApp(get(complete(StatusCodes.OK))) - Get(s"/api/v1/test") - .withHeaders(Origin(HttpOrigin("https", Host("invalid.foo.bar.com")))) ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - headers should contain(`Access-Control-Allow-Origin`(HttpOriginRange.*)) - } - } - - it should "respond with Pragma and Cache-Control (no-cache) headers" in { - val route = new TestApp(get(complete(StatusCodes.OK))) - Get(s"/api/v1/test") ~> route.appRoute ~> check { - status shouldBe StatusCodes.OK - header("Pragma").map(_.value()) should contain("no-cache") - header[`Cache-Control`].map(_.value()) should contain("no-cache") - } - } -} diff --git a/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala b/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala deleted file mode 100644 index cc0019a..0000000 --- a/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala +++ /dev/null @@ -1,121 +0,0 @@ -package xyz.driver.core.rest - -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.model.headers.Connection -import akka.http.scaladsl.server.Directives.{complete => akkaComplete} -import akka.http.scaladsl.server.{Directives, RejectionHandler, Route} -import akka.http.scaladsl.testkit.ScalatestRouteTest -import com.typesafe.scalalogging.Logger -import org.scalatest.{AsyncFlatSpec, Matchers} -import xyz.driver.core.json.serviceExceptionFormat -import xyz.driver.core.logging.NoLogger -import xyz.driver.core.rest.errors._ - -import scala.concurrent.Future - -class DriverRouteTest - extends AsyncFlatSpec with ScalatestRouteTest with SprayJsonSupport with Matchers with Directives { - class TestRoute(override val route: Route) extends DriverRoute { - override def log: Logger = NoLogger - } - - "DriverRoute" should "respond with 200 OK for a basic route" in { - val route = new TestRoute(akkaComplete(StatusCodes.OK)) - - Get("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.OK - } - } - - it should "respond with a 401 for an InvalidInputException" in { - val route = new TestRoute(akkaComplete(Future.failed[String](InvalidInputException()))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.BadRequest - responseAs[ServiceException] shouldBe InvalidInputException() - } - } - - it should "respond with a 403 for InvalidActionException" in { - val route = new TestRoute(akkaComplete(Future.failed[String](InvalidActionException()))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.Forbidden - responseAs[ServiceException] shouldBe InvalidActionException() - } - } - - it should "respond with a 404 for ResourceNotFoundException" in { - val route = new TestRoute(akkaComplete(Future.failed[String](ResourceNotFoundException()))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.NotFound - responseAs[ServiceException] shouldBe ResourceNotFoundException() - } - } - - it should "respond with a 500 for ExternalServiceException" in { - val error = ExternalServiceException("GET /api/v1/users/", "Permission denied", None) - val route = new TestRoute(akkaComplete(Future.failed[String](error))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.InternalServerError - responseAs[ServiceException] shouldBe error - } - } - - it should "allow pass-through of external service exceptions" in { - val innerError = InvalidInputException() - val error = ExternalServiceException("GET /api/v1/users/", "Permission denied", Some(innerError)) - val future = Future.failed[String](error) - val route = new TestRoute(akkaComplete(future.passThroughExternalServiceException)) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.BadRequest - responseAs[ServiceException] shouldBe innerError - } - } - - it should "respond with a 503 for ExternalServiceTimeoutException" in { - val error = ExternalServiceTimeoutException("GET /api/v1/users/") - val route = new TestRoute(akkaComplete(Future.failed[String](error))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.GatewayTimeout - responseAs[ServiceException] shouldBe error - } - } - - it should "respond with a 500 for DatabaseException" in { - val route = new TestRoute(akkaComplete(Future.failed[String](DatabaseException()))) - - Post("/api/v1/foo/bar") ~> route.routeWithDefaults ~> check { - handled shouldBe true - status shouldBe StatusCodes.InternalServerError - responseAs[ServiceException] shouldBe DatabaseException() - } - } - - it should "add a `Connection: close` header to avoid clashing with envoy's timeouts" in { - val rejectionHandler = RejectionHandler.newBuilder().handleNotFound(complete(StatusCodes.NotFound)).result() - val route = new TestRoute(handleRejections(rejectionHandler)((get & path("foo"))(complete("OK")))) - - Get("/foo") ~> route.routeWithDefaults ~> check { - status shouldBe StatusCodes.OK - headers should contain(Connection("close")) - } - - Get("/bar") ~> route.routeWithDefaults ~> check { - status shouldBe StatusCodes.NotFound - headers should contain(Connection("close")) - } - } -} diff --git a/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala b/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala deleted file mode 100644 index 987717d..0000000 --- a/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala +++ /dev/null @@ -1,101 +0,0 @@ -package xyz.driver.core.rest - -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import akka.http.scaladsl.model._ -import akka.http.scaladsl.model.headers.`Content-Type` -import akka.http.scaladsl.server.{Directives, Route} -import akka.http.scaladsl.testkit.ScalatestRouteTest -import org.scalatest.{FlatSpec, Matchers} -import spray.json._ -import xyz.driver.core.{Id, Name} -import xyz.driver.core.json._ - -import scala.concurrent.Future - -class PatchDirectivesTest - extends FlatSpec with Matchers with ScalatestRouteTest with SprayJsonSupport with DefaultJsonProtocol - with Directives with PatchDirectives { - case class Bar(name: Name[Bar], size: Int) - case class Foo(id: Id[Foo], name: Name[Foo], rank: Int, bar: Option[Bar]) - implicit val barFormat: RootJsonFormat[Bar] = jsonFormat2(Bar) - implicit val fooFormat: RootJsonFormat[Foo] = jsonFormat4(Foo) - - val testFoo: Foo = Foo(Id("1"), Name(s"Foo"), 1, Some(Bar(Name("Bar"), 10))) - - def route(retrieve: => Future[Option[Foo]]): Route = - Route.seal(path("api" / "v1" / "foos" / IdInPath[Foo]) { fooId => - entity(as[Patchable[Foo]]) { fooPatchable => - mergePatch(fooPatchable, retrieve) { updatedFoo => - complete(updatedFoo) - } - } - }) - - val MergePatchContentType = ContentType(`application/merge-patch+json`) - val ContentTypeHeader = `Content-Type`(MergePatchContentType) - def jsonEntity(json: String, contentType: ContentType.NonBinary = MergePatchContentType): RequestEntity = - HttpEntity(contentType, json) - - "PatchSupport" should "allow partial updates to an existing object" in { - val fooRetrieve = Future.successful(Some(testFoo)) - - Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - responseAs[Foo] shouldBe testFoo.copy(rank = 4) - } - } - - it should "merge deeply nested objects" in { - val fooRetrieve = Future.successful(Some(testFoo)) - - Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4, "bar": {"name": "My Bar"}}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - responseAs[Foo] shouldBe testFoo.copy(rank = 4, bar = Some(Bar(Name("My Bar"), 10))) - } - } - - it should "return a 404 if the object is not found" in { - val fooRetrieve = Future.successful(None) - - Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - status shouldBe StatusCodes.NotFound - } - } - - it should "handle nulls on optional values correctly" in { - val fooRetrieve = Future.successful(Some(testFoo)) - - Patch("/api/v1/foos/1", jsonEntity("""{"bar": null}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - responseAs[Foo] shouldBe testFoo.copy(bar = None) - } - } - - it should "handle optional values correctly when old value is null" in { - val fooRetrieve = Future.successful(Some(testFoo.copy(bar = None))) - - Patch("/api/v1/foos/1", jsonEntity("""{"bar": {"name": "My Bar","size":10}}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - responseAs[Foo] shouldBe testFoo.copy(bar = Some(Bar(Name("My Bar"), 10))) - } - } - - it should "return a 400 for nulls on non-optional values" in { - val fooRetrieve = Future.successful(Some(testFoo)) - - Patch("/api/v1/foos/1", jsonEntity("""{"rank": null}""")) ~> route(fooRetrieve) ~> check { - handled shouldBe true - status shouldBe StatusCodes.BadRequest - } - } - - it should "return a 415 for incorrect Content-Type" in { - val fooRetrieve = Future.successful(Some(testFoo)) - - Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4}""", ContentTypes.`application/json`)) ~> route(fooRetrieve) ~> check { - status shouldBe StatusCodes.UnsupportedMediaType - responseAs[String] should include("application/merge-patch+json") - } - } -} diff --git a/src/test/scala/xyz/driver/core/rest/RestTest.scala b/src/test/scala/xyz/driver/core/rest/RestTest.scala deleted file mode 100644 index 19e4ed1..0000000 --- a/src/test/scala/xyz/driver/core/rest/RestTest.scala +++ /dev/null @@ -1,151 +0,0 @@ -package xyz.driver.core.rest - -import akka.http.scaladsl.model.StatusCodes -import akka.http.scaladsl.server.{Directives, Route, ValidationRejection} -import akka.http.scaladsl.testkit.ScalatestRouteTest -import akka.util.ByteString -import org.scalatest.{Matchers, WordSpec} -import xyz.driver.core.rest - -import scala.concurrent.Future -import scala.util.Random - -class RestTest extends WordSpec with Matchers with ScalatestRouteTest with Directives { - "`escapeScriptTags` function" should { - "escape script tags properly" in { - val dirtyString = "</sc----</sc----</sc" - val cleanString = "--------------------" - - (escapeScriptTags(ByteString(dirtyString)).utf8String) should be(dirtyString.replace("</sc", "< /sc")) - - (escapeScriptTags(ByteString(cleanString)).utf8String) should be(cleanString) - } - } - - "paginated directive" should { - val route: Route = rest.paginated { paginated => - complete(StatusCodes.OK -> s"${paginated.pageNumber},${paginated.pageSize}") - } - "accept a pagination" in { - Get("/?pageNumber=2&pageSize=42") ~> route ~> check { - assert(status == StatusCodes.OK) - assert(entityAs[String] == "2,42") - } - } - "provide a default pagination" in { - Get("/") ~> route ~> check { - assert(status == StatusCodes.OK) - assert(entityAs[String] == "1,100") - } - } - "provide default values for a partial pagination" in { - Get("/?pageSize=2") ~> route ~> check { - assert(status == StatusCodes.OK) - assert(entityAs[String] == "1,2") - } - } - "reject an invalid pagination" in { - Get("/?pageNumber=-1") ~> route ~> check { - assert(rejection.isInstanceOf[ValidationRejection]) - } - } - } - - "optional paginated directive" should { - val route: Route = rest.optionalPagination { paginated => - complete(StatusCodes.OK -> paginated.map(p => s"${p.pageNumber},${p.pageSize}").getOrElse("no pagination")) - } - "accept a pagination" in { - Get("/?pageNumber=2&pageSize=42") ~> route ~> check { - assert(status == StatusCodes.OK) - assert(entityAs[String] == "2,42") - } - } - "without pagination" in { - Get("/") ~> route ~> check { - assert(status == StatusCodes.OK) - assert(entityAs[String] == "no pagination") - } - } - "reject an invalid pagination" in { - Get("/?pageNumber=1") ~> route ~> check { - assert(rejection.isInstanceOf[ValidationRejection]) - } - } - } - - "completeWithPagination directive" when { - import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ - import spray.json.DefaultJsonProtocol._ - - val data = Seq.fill(103)(Random.alphanumeric.take(10).mkString) - val route: Route = - parameter('empty.as[Boolean] ? false) { isEmpty => - completeWithPagination[String] { - case Some(pagination) if isEmpty => - Future.successful(ListResponse(Seq(), 0, Some(pagination))) - case Some(pagination) => - val filtered = data.slice(pagination.offset, pagination.offset + pagination.pageSize) - Future.successful(ListResponse(filtered, data.size, Some(pagination))) - case None if isEmpty => Future.successful(ListResponse(Seq(), 0, None)) - case None => Future.successful(ListResponse(data, data.size, None)) - } - } - - "pagination is defined" should { - "return a response with pagination headers" in { - Get("/?pageNumber=2&pageSize=10") ~> route ~> check { - responseAs[Seq[String]] shouldBe data.slice(10, 20) - header(ContextHeaders.ResourceCount).map(_.value) should contain("103") - header(ContextHeaders.PageCount).map(_.value) should contain("11") - } - } - - "disallow pageSize <= 0" in { - Get("/?pageNumber=2&pageSize=0") ~> route ~> check { - rejection shouldBe a[ValidationRejection] - } - - Get("/?pageNumber=2&pageSize=-1") ~> route ~> check { - rejection shouldBe a[ValidationRejection] - } - } - - "disallow pageNumber <= 0" in { - Get("/?pageNumber=0&pageSize=10") ~> route ~> check { - rejection shouldBe a[ValidationRejection] - } - - Get("/?pageNumber=-1&pageSize=10") ~> route ~> check { - rejection shouldBe a[ValidationRejection] - } - } - - "return PageCount == 0 if returning an empty list" in { - Get("/?empty=true&pageNumber=2&pageSize=10") ~> route ~> check { - responseAs[Seq[String]] shouldBe empty - header(ContextHeaders.ResourceCount).map(_.value) should contain("0") - header(ContextHeaders.PageCount).map(_.value) should contain("0") - } - } - } - - "pagination is not defined" should { - "return a response with pagination headers and PageCount == 1" in { - Get("/") ~> route ~> check { - responseAs[Seq[String]] shouldBe data - header(ContextHeaders.ResourceCount).map(_.value) should contain("103") - header(ContextHeaders.PageCount).map(_.value) should contain("1") - } - } - - "return PageCount == 0 if returning an empty list" in { - Get("/?empty=true") ~> route ~> check { - responseAs[Seq[String]] shouldBe empty - header(ContextHeaders.ResourceCount).map(_.value) should contain("0") - header(ContextHeaders.PageCount).map(_.value) should contain("0") - } - } - } - } -} |