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
|
package xyz.driver.core.rest
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, RequestEntity, StatusCodes}
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 PatchSupportTest
extends FlatSpec with Matchers with ScalatestRouteTest with SprayJsonSupport with DefaultJsonProtocol
with Directives with PatchSupport {
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(implicit patchRetrievable: PatchRetrievable[Foo]): Route =
Route.seal(path("api" / "v1" / "foos" / IdInPath[Foo]) { fooId =>
patch(as[Foo], fooId) { patchedFoo =>
complete(patchedFoo)
}
})
def jsonEntity(json: String): RequestEntity = HttpEntity(ContentTypes.`application/json`, json)
"PatchSupport" should "allow partial updates to an existing object" in {
implicit val fooPatchable = PatchRetrievable[Foo]((id, _) => Future.successful(Some(testFoo.copy(id = id))))
Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4}""")) ~> route ~> check {
handled shouldBe true
responseAs[Foo] shouldBe testFoo.copy(rank = 4)
}
}
it should "merge deeply nested objects" in {
implicit val fooPatchable = PatchRetrievable[Foo]((id, _) => Future.successful(Some(testFoo.copy(id = id))))
Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4, "bar": {"name": "My Bar"}}""")) ~> route ~> 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 {
implicit val fooPatchable = PatchRetrievable[Foo]((id, _) => Future.successful(None))
Patch("/api/v1/foos/1", jsonEntity("""{"rank": 4}""")) ~> route ~> check {
handled shouldBe true
status shouldBe StatusCodes.NotFound
}
}
it should "handle nulls on optional values correctly" in {
implicit val fooPatchable = PatchRetrievable[Foo]((id, _) => Future.successful(Some(testFoo.copy(id = id))))
Patch("/api/v1/foos/1", jsonEntity("""{"bar": null}""")) ~> route ~> check {
handled shouldBe true
responseAs[Foo] shouldBe testFoo.copy(bar = None)
}
}
it should "return a 400 for nulls on non-optional values" in {
implicit val fooPatchable = PatchRetrievable[Foo]((id, _) => Future.successful(Some(testFoo.copy(id = id))))
Patch("/api/v1/foos/1", jsonEntity("""{"rank": null}""")) ~> route ~> check {
handled shouldBe true
status shouldBe StatusCodes.BadRequest
}
}
}
|