소스 검색

Cleaned up unit tests and started on GET tests.

Tom Flucke 7 년 전
부모
커밋
8cdfc8e0ec
3개의 변경된 파일148개의 추가작업 그리고 110개의 파일을 삭제
  1. 32 19
      app/controllers/FoodController.scala
  2. 3 3
      conf/routes
  3. 113 88
      test/controllers/FoodControllerSpec.scala

+ 32 - 19
app/controllers/FoodController.scala

@@ -1,14 +1,16 @@
 package controllers
 
 import scala.concurrent.Await
-import scala.concurrent.duration.Duration
+import scala.language.postfixOps
+import scala.concurrent.duration.{Duration,DurationInt}
 import javax.inject._
-import models.{Food,Unit,Mass,Measure}
-import play.api.mvc._
-import play.api.libs.json._
-import play.api.test._
+import models.Food
+import org.bson.conversions.Bson
+import org.mongodb.scala.{MongoCollection, MongoDatabase, Observable, Completed}
 import org.mongodb.scala.model.Filters
-import org.mongodb.scala.{MongoDatabase,MongoCollection,Observable,Completed}
+import org.mongodb.scala.result.UpdateResult
+import play.api.libs.json._
+import play.api.mvc._
 
 @Singleton
 class FoodController(collection: MongoCollection[Food], sync: Boolean = true) extends Controller {
@@ -21,11 +23,11 @@ class FoodController(collection: MongoCollection[Food], sync: Boolean = true) ex
 
   def put(): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
     try {
-      val food: Food = request.body.as[Food]
+      val food: Food = request.body.as[Food](Food.foodFormats)
       val query: Observable[Completed] = collection.insertOne(food)
       if (sync)
       {
-        Await.result(query.toFuture, Duration.Inf)
+        Await.result(query.toFuture, 5 minutes)
       }
       Ok(Json.toJson(food))
     }
@@ -36,27 +38,38 @@ class FoodController(collection: MongoCollection[Food], sync: Boolean = true) ex
 
   def update(id: String): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
     try {
-      val query: Observable[Food] = collection.find(Filters.equal[String]("_id", id))
-      val food: Food = Await.result(query.toFuture, Duration.Inf)
-      val newJson: JsObject = Json.toJson(expFood) ++ request.body
-      val save: Observable[Completed] = collection.save(food.as[Food])
+      val filter: Bson = Filters.equal[String]("_id", id)
+      val json: JsObject = request.body.asInstanceOf[JsObject]
+      val queryResult: Observable[Food] = collection.find(filter)
+      val food: JsValue = Json.toJson(Await.result(queryResult.toFuture, 5 minutes))
+        .asInstanceOf[JsObject] ++ json
+      val save: Observable[UpdateResult] = collection.replaceOne(filter, food.as[Food])
       if (sync)
       {
-        Await.result(query.toFuture, Duration.Inf)
+        Await.result(save.toFuture, 5 minutes)
       }
-      Ok(newJson)
+      Ok(food)
     }
     catch {
       case jsre: JsResultException => BadRequest(s"Could not parse json into Food.")
     }
   }
-  def get(id: Int): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
-    Ok(Json.toJson(expFood))
+
+  def get(id: String): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
+      val filter: Bson = Filters.equal[String]("_id", id)
+      val queryResult: Observable[Food] = collection.find(filter)
+      Ok(Json.toJson(Await.result(queryResult.toFuture, 5 minutes)))
   }
+
   def query(query: String = ""): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
-    Ok(Json.toJson(expFood))
+      val filter: Bson = Filters.regex("name", s".*${query}.*")
+      val queryResult: Observable[Food] = collection.find(filter)
+      Ok(Json.toJson(Await.result(queryResult.toFuture, 5 minutes)))
   }
-  def delete(id: Int): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
-    Ok(request.body)
+
+  def delete(id: String): Action[JsValue] = Action(parse.json) { request: Request[JsValue] =>
+      val filter: Bson = Filters.equal[String]("_id", id)
+      val queryResult: Observable[Food] = collection.findOneAndDelete(filter)
+      Ok(Json.toJson(Await.result(queryResult.toFuture, 5 minutes)))
   }
 }

+ 3 - 3
conf/routes

@@ -8,10 +8,10 @@ GET     /                           controllers.HomeController.index
 
 # Food Manager
 GET     /food                       controllers.FoodController.query(term: String ?= "")
-GET     /food/:id                   controllers.FoodController.get(id: Int)
+GET     /food/:id                   controllers.FoodController.get(id: String)
 PUT     /food                       controllers.FoodController.put()
-POST    /food/:id                   controllers.FoodController.update(id: Int)
-DELETE  /food/:id                   controllers.FoodController.delete(id: Int)
+POST    /food/:id                   controllers.FoodController.update(id: String)
+DELETE  /food/:id                   controllers.FoodController.delete(id: String)
 
 # Ingredient Manager
 GET     /ingredients                controllers.IngredientController.editorPage

+ 113 - 88
test/controllers/FoodControllerSpec.scala

@@ -1,7 +1,8 @@
 package controllers
 
-import scala.concurrent.Await
-import scala.concurrent.duration.Duration
+import scala.concurrent.{Await,Future}
+import scala.language.postfixOps
+import scala.concurrent.duration.{Duration,DurationInt}
 import org.scalatestplus.play._
 import akka.stream.Materializer
 
@@ -10,16 +11,17 @@ import play.api.test._
 import play.api.test.Helpers._
 import play.api.http.Status
 import play.api.http.HeaderNames
-import play.api.libs.json.{JsValue,JsObject,Json}
+import play.api.libs.json.{JsValue,JsObject,Json,JsString}
 
 import org.scalatest._
 import org.scalatestplus.play._
 import org.scalatestplus.play.guice._
 
 import com.github.simplyscala.{MongoEmbedDatabase,MongodProps}
-import org.mongodb.scala.{MongoClient,MongoDatabase,MongoCollection,Observer};
+import org.mongodb.scala.{MongoClient,MongoDatabase,MongoCollection,Observer,Completed};
 
 import models.Food
+import models.{Mass,Volume}
 
 class FoodControllerSpec
     extends PlaySpec
@@ -30,27 +32,13 @@ class FoodControllerSpec
   val testDBPort: Int = 12345
   implicit lazy val materializer: Materializer = app.materializer
 
-  def getDBColl(): MongoCollection[Food] = {
-    val db: MongoDatabase = MongoClient.apply(s"mongodb://localhost:${testDBPort}")
-      .getDatabase("recipes")
-      .withCodecRegistry(MongoDB.codecRegistry)
-    db.createCollection("Food")
-    db.getCollection[Food]("Food")
-  }
+  val exampleFood1: Food = Food("1", "Example Food", false, true, false, Map("iron" -> 1.2f, "B" -> 7f),
+    "Test Script", Seq("Breakfast", "Asian"), Mass(), 3.2f, 2.7f, 1237, Seq("Scrambled Eggs"))
 
-  // def blockingObserver[T](sem: Semaphore, processor: T => Unit) = new Observer[T] {
-  //   override def onNext(result: T) = {println("next");processor(result)}
-  //   override def onError(e: Throwable) = {
-  //     println("error")
-  //     sem.release()
-  //     throw e
-  //   }
-  //   override def onComplete() = {println("complete");sem.release()}
-  // }
+  val exampleFood2: Food = Food("2", "Food Example", true, false, false, Map("copper" -> .2f, "D" -> 1f),
+    "Test Script", Seq("Dinner", "Southern"), Volume(), 1.2f, 2.0f, 73, Seq("Bacon"))
 
-  "FoodController PUT /food" should {
-    "return the resulting food object and insert into the DB" in {
-      val body: JsValue = Json.parse(
+  val exampleJson1: JsValue = Json.parse(
         """{"name": "Example Food",
       |"glutenFree": false,
       |"vegitarian": true,
@@ -64,8 +52,71 @@ class FoodControllerSpec
       |"price": 1237,
       |"alternatives": ["Scrambled Eggs"]
       |} """.stripMargin
-      )
-      val fakeRequ: FakeRequest[JsValue] = FakeRequest[JsValue](PUT, "/food", FakeHeaders(), body)
+  )
+
+  val strippedExampleJson1: JsValue = Json.parse(
+        """{"name": "Example Food",
+      |"vegitarian": true,
+      |"nutrients": {"iron": 1.2, "B": 7},
+      |"source": "Test Script",
+      |"category": ["Breakfast", "Asian"],
+      |"primaryMeasure": "mass",
+      |"density": 3.2,
+      |"mass_p_u": 2.7,
+      |"price": 1237,
+      |"alternatives": ["Scrambled Eggs"]
+      |} """.stripMargin
+  )
+
+  val brokenExampleJson1: JsValue = Json.parse(
+        """{
+      |"glutenFree": false,
+      |"vegitarian": true,
+      |"vegan": false,
+      |"nutrients": {"iron": 1.2, "B": 7},
+      |"source": "Test Script",
+      |"category": ["Breakfast", "Asian"],
+      |"primaryMeasure": "mass",
+      |"density": 3.2,
+      |"mass_p_u": 2.7,
+      |"price": 1237,
+      |"alternatives": ["Scrambled Eggs"]
+      |} """.stripMargin
+  )
+
+  val exampleJson2: JsValue = Json.parse(
+        """{"name": "Food Example",
+      |"glutenFree": true,
+      |"vegitarian": false,
+      |"vegan": false,
+      |"nutrients": {"copper": 0.2, "D": 1},
+      |"source": "Test Script",
+      |"category": ["Dinner", "Southern"],
+      |"primaryMeasure": "volume",
+      |"density": 1.2,
+      |"mass_p_u": 2,
+      |"price": 73,
+      |"alternatives": ["Bacon"]
+      |} """.stripMargin
+  )
+
+  def getDBColl(): MongoCollection[Food] = {
+    val db: MongoDatabase = MongoClient.apply(s"mongodb://localhost:${testDBPort}")
+      .getDatabase("recipes")
+      .withCodecRegistry(MongoDB.codecRegistry)
+    db.createCollection("Food")
+    db.getCollection[Food]("Food")
+  }
+
+  def initDB(coll: MongoCollection[Food], items: Seq[Food]): MongoCollection[Food] = {
+    val futures: Seq[Future[Completed]] = items.map(coll.insertOne(_).toFuture)
+    futures.map(Await.ready(_, 5 minutes))
+    coll
+  }
+
+  "FoodController PUT /food" should {
+    "return the resulting food object and insert into the DB" in {
+      val fakeRequ: FakeRequest[JsValue] = FakeRequest[JsValue](PUT, "/food", FakeHeaders(), exampleJson1)
         .withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")
       withEmbedMongoFixture() { mongodProps: MongodProps =>
         val coll: MongoCollection[Food] = getDBColl
@@ -74,22 +125,12 @@ class FoodControllerSpec
         status(result) mustBe OK
         contentType(result) mustBe Some("application/json")
         val resultJs: JsObject = contentAsJson(result).asInstanceOf[JsObject]
-        resultJs - "_id" mustBe body
-        Await.result(coll.countDocuments().toFuture, Duration.Inf) mustBe 1
-        Await.result(coll.find().first().toFuture, Duration.Inf) mustBe resultJs.as[Food]
+        resultJs - "_id" mustBe exampleJson1
+        Await.result(coll.countDocuments().toFuture, 5 minutes) mustBe 1
+        Await.result(coll.find().first().toFuture, 5 minutes) mustBe resultJs.as[Food]
       }
     }
 
-    // Removed: not presenting id's to front-end
-    // "return the resulting food object with a distinct id" in {
-    //   val controller = inject[HomeController]
-    //   val home = controller.index().apply(FakeRequest(GET, "/"))
-
-    //   status(home) mustBe OK
-    //   contentType(home) mustBe Some("text/html")
-    //   contentAsString(home) must include ("Welcome to Play")
-    // }
-
     "return an error when given an empty request and not " +
       "insert into the DB" in {
         val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), "")
@@ -99,69 +140,26 @@ class FoodControllerSpec
           val controller: FoodController = new FoodController(coll)
           val result = controller.put().apply(fakeRequ)
           status(result) mustBe Status.BAD_REQUEST
-          Await.result(coll.countDocuments().toFuture, Duration.Inf) mustBe 0
+          Await.result(coll.countDocuments().toFuture, 5 minutes) mustBe 0
         }
       }
 
     "return an error when given an invalid request and not " +
       "insert into the DB" in {
-        val body: JsValue = Json.parse(// Name intentionally moved
-          """{
-      |"glutenFree": false,
-      |"vegitarian": true,
-      |"vegan": false,
-      |"nutrients": {"iron": 1.2, "B": 7},
-      |"source": "Test Script",
-      |"category": ["Breakfast", "Asian"],
-      |"primaryMeasure": "mass",
-      |"density": 3.2,
-      |"mass_p_u": 2.7,
-      |"price": 1237,
-      |"alternatives": ["Scrambled Eggs"]
-      |} """.stripMargin
-        )
-        val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), body)
+        val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), brokenExampleJson1)
         .withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")
         withEmbedMongoFixture() { mongodProps: MongodProps =>
           val coll: MongoCollection[Food] = getDBColl
           val controller: FoodController = new FoodController(coll)
           val result = controller.put().apply(fakeRequ)
           status(result) mustBe Status.BAD_REQUEST
-          Await.result(coll.countDocuments().toFuture, Duration.Inf) mustBe 0
+          Await.result(coll.countDocuments().toFuture, 5 minutes) mustBe 0
         }
       }
 
     "return the resulting object with default values and " +
     "insert into the DB" in {
-      val body: JsValue = Json.parse(
-        """{"name": "Example Food",
-      |"vegitarian": true,
-      |"nutrients": {"iron": 1.2, "B": 7},
-      |"source": "Test Script",
-      |"category": ["Breakfast", "Asian"],
-      |"primaryMeasure": "mass",
-      |"density": 3.2,
-      |"mass_p_u": 2.7,
-      |"price": 1237,
-      |"alternatives": ["Scrambled Eggs"]
-      |} """.stripMargin
-      )
-      val expected: JsValue = Json.parse(
-        """{"name": "Example Food",
-      |"glutenFree": false,
-      |"vegitarian": true,
-      |"vegan": false,
-      |"nutrients": {"iron": 1.2, "B": 7},
-      |"source": "Test Script",
-      |"category": ["Breakfast", "Asian"],
-      |"primaryMeasure": "mass",
-      |"density": 3.2,
-      |"mass_p_u": 2.7,
-      |"price": 1237,
-      |"alternatives": ["Scrambled Eggs"]
-      |} """.stripMargin
-      )
-      val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), body)
+      val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), strippedExampleJson1)
         .withHeaders(HeaderNames.CONTENT_TYPE -> "application/json")
       withEmbedMongoFixture() { mongodProps: MongodProps =>
         val coll: MongoCollection[Food] = getDBColl
@@ -170,10 +168,37 @@ class FoodControllerSpec
         status(result) mustBe OK
         contentType(result) mustBe Some("application/json")
         val resultJs: JsObject = contentAsJson(result).asInstanceOf[JsObject]
-        resultJs - "_id"  mustBe expected
-        Await.result(coll.countDocuments().toFuture, Duration.Inf) mustBe 1
-        Await.result(coll.find().first().toFuture, Duration.Inf) mustBe resultJs.as[Food]
+        resultJs - "_id"  mustBe exampleJson1
+        Await.result(coll.countDocuments().toFuture, 5 minutes) mustBe 1
+        Await.result(coll.find().first().toFuture, 5 minutes) mustBe resultJs.as[Food]
       }
     }
   }
+
+  "FoodController GET /food/:id" should {
+    "return the correct food object" in {
+      val fakeRequ: FakeRequest[JsValue] = FakeRequest[JsValue](GET, s"/food/${exampleFood1._id}", FakeHeaders(), JsString(""))
+      withEmbedMongoFixture() { mongodProps: MongodProps =>
+        val coll: MongoCollection[Food] = initDB(getDBColl, Seq(exampleFood1, exampleFood2))
+        val controller: FoodController = new FoodController(coll)
+        val result = controller.put().apply(fakeRequ)
+        status(result) mustBe OK
+        contentType(result) mustBe Some("application/json")
+        val resultJs: JsObject = contentAsJson(result).asInstanceOf[JsObject]
+        resultJs.value.get("_id").toString mustBe exampleFood1._id
+        resultJs - "_id" mustBe exampleJson1
+      }
+    }
+
+    "return an error when given an empty request and not " +
+      "insert into the DB" in {
+        val fakeRequ: FakeRequest[JsValue] = FakeRequest[JsValue](GET, s"/food/0", FakeHeaders(), JsString(""))
+        withEmbedMongoFixture() { mongodProps: MongodProps =>
+          val coll: MongoCollection[Food] = getDBColl
+          val controller: FoodController = new FoodController(coll)
+          val result = controller.put().apply(fakeRequ)
+          status(result) mustBe Status.NOT_FOUND
+        }
+      }
+  }
 }