package controllers import scala.concurrent.{Await,Future} import scala.language.postfixOps import scala.concurrent.duration.{Duration,DurationInt} import org.scalatestplus.play._ import akka.stream.Materializer import play.api.mvc._ 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,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,Completed}; import models.Food import models.{Mass,Volume} class FoodControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting with MongoEmbedDatabase { val testDBPort: Int = 12345 implicit lazy val materializer: Materializer = app.materializer 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")) 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")) val exampleJson1: 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 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 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 - "_id" mustBe exampleJson1 Await.result(coll.countDocuments().toFuture, 5 minutes) mustBe 1 Await.result(coll.find().first().toFuture, 5 minutes) mustBe resultJs.as[Food] } } "return an error when given an empty request and not " + "insert into the DB" in { val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), "") .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, 5 minutes) mustBe 0 } } "return an error when given an invalid request and not " + "insert into the DB" in { 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, 5 minutes) mustBe 0 } } "return the resulting object with default values and " + "insert into the DB" in { val fakeRequ = FakeRequest(PUT, "/food", FakeHeaders(), strippedExampleJson1) .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 OK contentType(result) mustBe Some("application/json") val resultJs: JsObject = contentAsJson(result).asInstanceOf[JsObject] 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 } } } }