Skip to content

Commit

Permalink
- Refactor PlayEnumSpec for DRYness and coverage
Browse files Browse the repository at this point in the history
- Fix bug found in PlayUppercaseQueryBindableEnum
  • Loading branch information
lloydmeta committed Jul 19, 2016
1 parent 9977ada commit cec8d3a
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 101 deletions.
Expand Up @@ -3,5 +3,5 @@ package enumeratum
import play.api.mvc.QueryStringBindable

trait PlayUppercaseQueryBindableEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val queryBindable: QueryStringBindable[A] = UrlBinders.queryBinderLowercaseOnly(self)
implicit val queryBindable: QueryStringBindable[A] = UrlBinders.queryBinderUppercaseOnly(self)
}
32 changes: 32 additions & 0 deletions enumeratum-play/src/test/scala/enumeratum/Models.scala
@@ -0,0 +1,32 @@
package enumeratum

/**
* Created by Lloyd on 2/4/15.
*/
sealed trait PlayDummyNormal extends EnumEntry

object PlayDummyNormal extends PlayEnum[PlayDummyNormal] {
case object A extends PlayDummyNormal
case object B extends PlayDummyNormal
case object c extends PlayDummyNormal
val values = findValues
}

sealed trait PlayDummyLowerOnly extends EnumEntry

object PlayDummyLowerOnly extends PlayLowercaseEnum[PlayDummyLowerOnly] {
case object A extends PlayDummyLowerOnly
case object B extends PlayDummyLowerOnly
case object c extends PlayDummyLowerOnly
val values = findValues
}

sealed trait PlayDummyUpperOnly extends EnumEntry

object PlayDummyUpperOnly extends PlayUppercaseEnum[PlayDummyUpperOnly] {
case object A extends PlayDummyUpperOnly
case object B extends PlayDummyUpperOnly
case object c extends PlayDummyUpperOnly
val values = findValues
}

13 changes: 0 additions & 13 deletions enumeratum-play/src/test/scala/enumeratum/PlayDummy.scala

This file was deleted.

259 changes: 172 additions & 87 deletions enumeratum-play/src/test/scala/enumeratum/PlayEnumSpec.scala
Expand Up @@ -3,132 +3,215 @@ package enumeratum
import java.security.cert.X509Certificate

import org.scalatest.{ FunSpec, Matchers }
import play.api.data.Form
import play.api.data.{ Form, Mapping }
import play.api.http.HttpVerbs
import play.api.libs.json.{ JsNumber, JsString, Json => PlayJson }
import play.api.libs.json.{ Format, JsNumber, JsString, JsValue, Json => PlayJson }
import org.scalatest.OptionValues._
import org.scalatest.EitherValues._
import play.api.mvc.{ Headers, RequestHeader }
import play.api.routing.Router
import play.api.mvc.{ Headers, PathBindable, QueryStringBindable, RequestHeader }
import play.api.routing.sird.PathBindableExtractor

class PlayEnumSpec extends FunSpec with Matchers {

describe("JSON serdes") {
testScenarios(
descriptor = "ordinary operation (no tarnsforms)",
enum = PlayDummyNormal,
validTransforms = Map("A" -> PlayDummyNormal.A, "B" -> PlayDummyNormal.B, "c" -> PlayDummyNormal.c),
expectedFailures = Seq("1.234"),
formMapping = PlayDummyNormal.formField,
pathBindable = PlayDummyNormal.pathBindable,
pathBindableExtractor = PlayDummyNormal.fromPath,
queryStringBindable = PlayDummyNormal.queryBindable
)

testScenarios(
descriptor = "lower case transformed",
enum = PlayDummyLowerOnly,
validTransforms = Map("a" -> PlayDummyLowerOnly.A, "b" -> PlayDummyLowerOnly.B, "c" -> PlayDummyLowerOnly.c),
expectedFailures = Seq("C"),
formMapping = PlayDummyLowerOnly.formField,
pathBindable = PlayDummyLowerOnly.pathBindable,
pathBindableExtractor = PlayDummyLowerOnly.fromPath,
queryStringBindable = PlayDummyLowerOnly.queryBindable
)

testScenarios(
descriptor = "upper case transformed",
enum = PlayDummyUpperOnly,
validTransforms = Map("A" -> PlayDummyUpperOnly.A, "B" -> PlayDummyUpperOnly.B, "C" -> PlayDummyUpperOnly.c),
expectedFailures = Seq("c"),
formMapping = PlayDummyUpperOnly.formField,
pathBindable = PlayDummyUpperOnly.pathBindable,
pathBindableExtractor = PlayDummyUpperOnly.fromPath,
queryStringBindable = PlayDummyUpperOnly.queryBindable
)

private def testScenarios[A <: EnumEntry: Format](
descriptor: String,
enum: Enum[A],
validTransforms: Map[String, A],
expectedFailures: Seq[String],
formMapping: Mapping[A],
pathBindable: PathBindable[A],
pathBindableExtractor: PathBindableExtractor[A],
queryStringBindable: QueryStringBindable[A]
): Unit = describe(descriptor) {

testJson()
testFormBinding()
testUrlBinding()

def testJson(): Unit = {

val failures: Seq[JsValue] = expectedFailures.map(JsString) ++ Seq(JsString("AVADSGDSAFA"), JsNumber(Int.MaxValue))

describe("JSON serdes") {

describe("deserialisation") {

it("should work with valid values") {
validTransforms.foreach {
case (k, v) =>
JsString(k).asOpt[A].value shouldBe v
}
}

describe("deserialisation") {
it("should fail with invalid values") {
failures.foreach { v =>
v.asOpt[A] shouldBe None
}
}
}

it("should work with valid values") {
JsString("A").asOpt[PlayDummy].value shouldBe PlayDummy.A
}
describe("serialisation") {

it("should fail with invalid values") {
JsString("D").asOpt[PlayDummy] shouldBe None
JsNumber(2).asOpt[PlayDummy] shouldBe None
}
}
it("should serialise values to JsString") {
validTransforms.foreach {
case (k, v) =>
PlayJson.toJson(v) shouldBe JsString(k)
}
}

describe("serialisation") {
}

it("should serialise values to JsString") {
PlayJson.toJson(PlayDummy.A) shouldBe JsString("A")
}

}

}

describe("Form binding") {
def testFormBinding(): Unit = {

val subject = Form("hello" -> PlayDummy.formField)
val subject = Form("hello" -> formMapping)
val expectedErrors = expectedFailures ++ Seq(Int.MaxValue.toString, "12asdf13!")

it("should bind proper strings into an Enum value") {
val r1 = subject.bind(Map("hello" -> "A"))
val r2 = subject.bind(Map("hello" -> "B"))
r1.value.value shouldBe PlayDummy.A
r2.value.value shouldBe PlayDummy.B
}
describe("Form binding") {
it("should bind proper strings into an Enum value") {
validTransforms.foreach {
case (k, v) =>
val r = subject.bind(Map("hello" -> k))
r.value.value shouldBe v
}
}

it("should fail to bind random strings") {
val r = subject.bind(Map("hello" -> "AARSE"))
r.value shouldBe None
it("should fail to bind random strings") {
expectedErrors.foreach { s =>
val r = subject.bind(Map("hello" -> s))
r.value shouldBe None
}
}
}
}

}

describe("URL binding") {

describe("PathBindable") {

val subject = PlayDummy.pathBindable
def testUrlBinding(): Unit = {

it("should bind strings corresponding to enum strings") {
subject.bind("hello", "A").right.value shouldBe PlayDummy.A
}
val expectedErrors = expectedFailures ++ Seq("1", "abc123", "Z", "F")

it("should not bind strings not found in the enumeration") {
subject.bind("hello", "Z").isLeft shouldBe true
}
describe("URL Binding") {

it("should unbind values") {
subject.unbind("hello", PlayDummy.A) shouldBe "A"
subject.unbind("hello", PlayDummy.B) shouldBe "B"
}
describe("PathBindable") {
it("should bind strings corresponding to enum strings") {
validTransforms.foreach {
case (k, v) =>
pathBindable.bind("hello", k).right.value shouldBe v
}
}

}
it("should not bind strings not found in the enumeration") {
expectedErrors.foreach { v =>
pathBindable.bind("hello", v).isLeft shouldBe true
}
}

describe("PathBindableExtractor") {
it("should unbind values") {
validTransforms.foreach {
case (k, v) =>
pathBindable.unbind("hello", v) shouldBe k
}
}
}

val subject = PlayDummy.fromPath
describe("PathBindableExtractor") {

it("should extract strings corresponding to enum strings") {
subject.unapply("A") shouldBe Some(PlayDummy.A)
subject.unapply("B") shouldBe Some(PlayDummy.B)
subject.unapply("C") shouldBe Some(PlayDummy.C)
}
it("should extract strings corresponding to enum strings") {
validTransforms.foreach {
case (k, v) =>
pathBindableExtractor.unapply(k) shouldBe Some(v)
}
}

it("should not extract strings that are not in the enumeration") {
subject.unapply("Z") shouldBe None
}
it("should not extract strings that are not in the enumeration") {
expectedErrors.foreach { v =>
pathBindableExtractor.unapply(v) shouldBe None
}
}

it("should allow me to build an SIRD router") {
import play.api.routing.sird._
import play.api.routing._
import play.api.mvc._
val router = Router.from {
case GET(p"/${ PlayDummy.fromPath(greeting) }") => Action {
Results.Ok(s"$greeting")
it("should allow me to build an SIRD router") {
import play.api.routing.sird._
import play.api.routing._
import play.api.mvc._
val router = Router.from {
case GET(p"/${ pathBindableExtractor(greeting) }") => Action {
Results.Ok(s"$greeting")
}
}
validTransforms.foreach {
case (k, v) =>
router.routes.isDefinedAt(reqHeaderAt(HttpVerbs.GET, s"/$k")) shouldBe true
}
expectedErrors.foreach { v =>
router.routes.isDefinedAt(reqHeaderAt(HttpVerbs.GET, s"/$v")) shouldBe false
}
}

}
router.routes.isDefinedAt(reqHeaderAt(HttpVerbs.GET, "/A")) shouldBe true
router.routes.isDefinedAt(reqHeaderAt(HttpVerbs.GET, "/F")) shouldBe false
}

}
describe("QueryStringBindable") {

describe("QueryStringBindable") {
it("should bind strings corresponding to enum strings") {
validTransforms.foreach {
case (k, v) =>
queryStringBindable.bind("hello", Map("hello" -> Seq(k))).value.right.value should be(v)
}
}

val subject = PlayDummy.queryBindable
it("should not bind strings not found in the enumeration") {
expectedErrors.foreach { v =>
queryStringBindable.bind("hello", Map("hello" -> Seq(v))).value shouldBe 'left
queryStringBindable.bind("hello", Map("helloz" -> Seq(v))) shouldBe None
}
}

it("should bind strings corresponding to enum strings regardless of case") {
subject.bind("hello", Map("hello" -> Seq("A"))).value.right.value should be(PlayDummy.A)
}
it("should unbind values") {
validTransforms.foreach {
case (k, v) =>
queryStringBindable.unbind("hello", v) shouldBe s"hello=$k"
}
}

it("should not bind strings not found in the enumeration") {
subject.bind("hello", Map("hello" -> Seq("Z"))).value should be('left)
subject.bind("hello", Map("helloz" -> Seq("A"))) shouldBe None
}
}

it("should unbind values") {
subject.unbind("hello", PlayDummy.A) should be("hello=A")
subject.unbind("hello", PlayDummy.B) should be("hello=B")
}

}

}

private def reqHeaderAt(theMethod: String, theUri: String) =
new RequestHeader {
def reqHeaderAt(theMethod: String, theUri: String) = new RequestHeader {

def clientCertificateChain: Option[Seq[X509Certificate]] = ???

Expand All @@ -153,4 +236,6 @@ class PlayEnumSpec extends FunSpec with Matchers {
def id: Long = ???
}

}

}

0 comments on commit cec8d3a

Please sign in to comment.