aboutsummaryrefslogtreecommitdiff
path: root/src/test/scala/xyz/driver/core/rest/DriverRouteTest.scala
blob: 60056b716b35e15aa64c9e15e78dc04013c0459e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package xyz.driver.core.rest

import akka.http.scaladsl.model.{HttpMethod, StatusCodes}
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Directives.{complete => akkaComplete}
import akka.http.scaladsl.server.{Directives, Route}
import akka.http.scaladsl.testkit.ScalatestRouteTest
import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.Logger
import org.scalatest.{AsyncFlatSpec, Matchers}
import xyz.driver.core.logging.NoLogger
import xyz.driver.core.rest.errors._

import scala.concurrent.Future

class DriverRouteTest extends AsyncFlatSpec with ScalatestRouteTest with Matchers with Directives {
  class TestRoute(override val route: Route) extends DriverRoute {
    override def log: Logger = NoLogger
    override def config: Config =
      ConfigFactory.parseString("""
                                  |application {
                                  |   cors {
                                  |     allowedMethods: ["GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS"]
                                  |     allowedOrigins: [{scheme: https, hostSuffix: example.com}]
                                  |   }
                                  |}
      """.stripMargin)
  }

  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)
  }

  "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[String] shouldBe "Invalid input"
    }
  }

  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[String] shouldBe "This action is not allowed"
    }
  }

  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[String] shouldBe "Resource not found"
    }
  }

  it should "respond with a 500 for ExternalServiceException" in {
    val error = ExternalServiceException("GET /api/v1/users/", "Permission denied")
    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[String] shouldBe "Error while calling 'GET /api/v1/users/': Permission denied"
    }
  }

  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[String] shouldBe "GET /api/v1/users/ took too long to respond"
    }
  }

  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[String] shouldBe "Database access error"
    }
  }

  it should "respond with the correct CORS headers for the swagger OPTIONS route" in {
    val route = new TestRoute(get(akkaComplete(StatusCodes.OK)))
    Options(s"/api-docs/swagger.json") ~> route.routeWithDefaults ~> 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 TestRoute(get(akkaComplete(StatusCodes.OK)))
    Options(s"/api/v1/test") ~> route.routeWithDefaults ~> 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 "allow subdomains of allowed origin suffixes" in {
    val route = new TestRoute(get(akkaComplete(StatusCodes.OK)))
    Options(s"/api/v1/test")
      .withHeaders(Origin(HttpOrigin("https", Host("foo.example.com")))) ~> route.routeWithDefaults ~> check {
      status shouldBe StatusCodes.OK
      headers should contain(`Access-Control-Allow-Origin`(HttpOrigin("https", Host("foo.example.com"))))
      header[`Access-Control-Allow-Methods`].get.methods should contain theSameElementsAs allowedMethods
    }
  }

  it should "respond with default domains for invalid origins" in {
    val route = new TestRoute(get(akkaComplete(StatusCodes.OK)))
    Options(s"/api/v1/test")
      .withHeaders(Origin(HttpOrigin("https", Host("invalid.foo.bar.com")))) ~> route.routeWithDefaults ~> 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
    }
  }
}