aboutsummaryrefslogtreecommitdiff
path: root/src/test/scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/scala')
-rw-r--r--src/test/scala/xyz/driver/core/JsonTest.scala10
-rw-r--r--src/test/scala/xyz/driver/core/PhoneNumberTest.scala79
-rw-r--r--src/test/scala/xyz/driver/core/TimeTest.scala36
-rw-r--r--src/test/scala/xyz/driver/core/database/DatabaseTest.scala1
-rw-r--r--src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala92
5 files changed, 217 insertions, 1 deletions
diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala
index a45025a..827624c 100644
--- a/src/test/scala/xyz/driver/core/JsonTest.scala
+++ b/src/test/scala/xyz/driver/core/JsonTest.scala
@@ -11,6 +11,7 @@ import xyz.driver.core.time.provider.SystemTimeProvider
import spray.json._
import xyz.driver.core.TestTypes.CustomGADT
import xyz.driver.core.domain.{Email, PhoneNumber}
+import xyz.driver.core.time.TimeOfDay
class JsonTest extends FlatSpec with Matchers {
import DefaultJsonProtocol._
@@ -61,6 +62,15 @@ class JsonTest extends FlatSpec with Matchers {
parsedTime should be(referenceTime)
}
+ "Json format for TimeOfDay" should "read and write correct JSON" in {
+ val utcTimeZone = java.util.TimeZone.getTimeZone("UTC")
+ val referenceTimeOfDay = TimeOfDay.parseTimeString(utcTimeZone)("08:00:00")
+ val writtenJson = json.timeOfDayFormat.write(referenceTimeOfDay)
+ writtenJson should be("""{"localTime":"08:00:00","timeZone":"UTC"}""".parseJson)
+ val parsed = json.timeOfDayFormat.read(writtenJson)
+ parsed should be(referenceTimeOfDay)
+ }
+
"Json format for Date" should "read and write correct JSON" in {
import date._
diff --git a/src/test/scala/xyz/driver/core/PhoneNumberTest.scala b/src/test/scala/xyz/driver/core/PhoneNumberTest.scala
new file mode 100644
index 0000000..384c7be
--- /dev/null
+++ b/src/test/scala/xyz/driver/core/PhoneNumberTest.scala
@@ -0,0 +1,79 @@
+package xyz.driver.core
+
+import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.domain.PhoneNumber
+
+class PhoneNumberTest extends FlatSpec with Matchers {
+
+ "PhoneNumber.parse" should "recognize US numbers in international format, ignoring non-digits" in {
+ // format: off
+ val numbers = List(
+ "+18005252225",
+ "+1 800 525 2225",
+ "+1 (800) 525-2225",
+ "+1.800.525.2225")
+ // format: on
+
+ val parsed = numbers.flatMap(PhoneNumber.parse)
+
+ parsed should have size numbers.size
+ parsed should contain only PhoneNumber("1", "8005252225")
+ }
+
+ it should "recognize US numbers without the plus sign" in {
+ PhoneNumber.parse("18005252225") shouldBe Some(PhoneNumber("1", "8005252225"))
+ }
+
+ it should "recognize US numbers without country code" in {
+ // format: off
+ val numbers = List(
+ "8005252225",
+ "800 525 2225",
+ "(800) 525-2225",
+ "800.525.2225")
+ // format: on
+
+ val parsed = numbers.flatMap(PhoneNumber.parse)
+
+ parsed should have size numbers.size
+ parsed should contain only PhoneNumber("1", "8005252225")
+ }
+
+ it should "recognize CN numbers in international format" in {
+ PhoneNumber.parse("+868005252225") shouldBe Some(PhoneNumber("86", "8005252225"))
+ PhoneNumber.parse("+86 134 52 52 2256") shouldBe Some(PhoneNumber("86", "13452522256"))
+ }
+
+ it should "return None on numbers that are shorter than the minimum number of digits for the country (i.e. US - 10, AR - 11)" in {
+ withClue("US and CN numbers are 10 digits - 9 digit (and shorter) numbers should not fit") {
+ // format: off
+ val numbers = List(
+ "+1 800 525-222",
+ "+1 800 525-2",
+ "+86 800 525-222",
+ "+86 800 525-2")
+ // format: on
+
+ numbers.flatMap(PhoneNumber.parse) shouldBe empty
+ }
+
+ withClue("Argentinian numbers are 11 digits (when prefixed with 0) - 10 digit numbers shouldn't fit") {
+ // format: off
+ val numbers = List(
+ "+54 011 525-22256",
+ "+54 011 525-2225",
+ "+54 011 525-222")
+ // format: on
+
+ numbers.flatMap(PhoneNumber.parse) should contain theSameElementsAs List(PhoneNumber("54", "1152522256"))
+ }
+ }
+
+ it should "return None on numbers that are longer than the maximum number of digits for the country (i.e. DK - 8, CN - 11)" in {
+ val numbers = List("+45 27 45 25 22", "+45 135 525 223", "+86 134 525 22256", "+86 135 525 22256 7")
+
+ numbers.flatMap(PhoneNumber.parse) should contain theSameElementsAs
+ List(PhoneNumber("45", "27452522"), PhoneNumber("86", "13452522256"))
+ }
+
+}
diff --git a/src/test/scala/xyz/driver/core/TimeTest.scala b/src/test/scala/xyz/driver/core/TimeTest.scala
index 2aa3edf..75588a2 100644
--- a/src/test/scala/xyz/driver/core/TimeTest.scala
+++ b/src/test/scala/xyz/driver/core/TimeTest.scala
@@ -7,6 +7,7 @@ import org.scalacheck.Prop.BooleanOperators
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.prop.Checkers
import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.date.Month
import xyz.driver.core.time.{Time, _}
import scala.concurrent.duration._
@@ -102,4 +103,39 @@ class TimeTest extends FlatSpec with Matchers with Checkers {
textualDate(EST)(timestamp) should not be textualDate(PST)(timestamp)
timestamp.toDate(EST) should not be timestamp.toDate(PST)
}
+
+ "TimeOfDay" should "be created from valid strings and convert to java.sql.Time" in {
+ val s = "07:30:45"
+ val defaultTimeZone = TimeZone.getDefault()
+ val todFactory = TimeOfDay.parseTimeString(defaultTimeZone)(_)
+ val tod = todFactory(s)
+ tod.timeString shouldBe s
+ tod.timeZoneString shouldBe defaultTimeZone.getID
+ val sqlTime = tod.toTime
+ sqlTime.toLocalTime shouldBe tod.localTime
+ a[java.time.format.DateTimeParseException] should be thrownBy {
+ val illegal = "7:15"
+ todFactory(illegal)
+ }
+ }
+
+ "TimeOfDay" should "have correct temporal relationships" in {
+ val s = "07:30:45"
+ val t = "09:30:45"
+ val pst = TimeZone.getTimeZone("America/Los_Angeles")
+ val est = TimeZone.getTimeZone("America/New_York")
+ val pstTodFactory = TimeOfDay.parseTimeString(pst)(_)
+ val estTodFactory = TimeOfDay.parseTimeString(est)(_)
+ val day = 1
+ val month = Month.JANUARY
+ val year = 2018
+ val sTodPst = pstTodFactory(s)
+ val sTodPst2 = pstTodFactory(s)
+ val tTodPst = pstTodFactory(t)
+ val tTodEst = estTodFactory(t)
+ sTodPst.isBefore(tTodPst, day, month, year) shouldBe true
+ tTodPst.isAfter(sTodPst, day, month, year) shouldBe true
+ tTodEst.isBefore(sTodPst, day, month, year) shouldBe true
+ sTodPst.sameTimeAs(sTodPst2, day, month, year) shouldBe true
+ }
}
diff --git a/src/test/scala/xyz/driver/core/database/DatabaseTest.scala b/src/test/scala/xyz/driver/core/database/DatabaseTest.scala
index f85dcad..8d2a4ac 100644
--- a/src/test/scala/xyz/driver/core/database/DatabaseTest.scala
+++ b/src/test/scala/xyz/driver/core/database/DatabaseTest.scala
@@ -39,5 +39,4 @@ class DatabaseTest extends FlatSpec with Matchers with Checkers {
an[DatabaseException] should be thrownBy TestConverter.expectValidOrEmpty(mapper, invalidOp)
}
-
}
diff --git a/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala b/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala
new file mode 100644
index 0000000..6a6b035
--- /dev/null
+++ b/src/test/scala/xyz/driver/core/rest/PatchDirectivesTest.scala
@@ -0,0 +1,92 @@
+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 "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")
+ }
+ }
+}