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