Add group-parameter to couch-view-execution

Signed-off-by: Hoang Anh Le <hoang@de.ibm.com>
diff --git a/tests/src/whisk/core/database/test/CouchDbRestClientTests.scala b/tests/src/whisk/core/database/test/CouchDbRestClientTests.scala
index 96bbee8..a2fe472 100644
--- a/tests/src/whisk/core/database/test/CouchDbRestClientTests.scala
+++ b/tests/src/whisk/core/database/test/CouchDbRestClientTests.scala
@@ -16,6 +16,7 @@
 
 package whisk.core.database.test
 
+import scala.concurrent.Await
 import scala.concurrent.Future
 import scala.concurrent.Promise
 import scala.concurrent.duration.DurationDouble
@@ -32,8 +33,8 @@
 
 import akka.actor.Props
 import akka.http.scaladsl.model._
-import akka.util.ByteString
 import akka.stream.scaladsl._
+import akka.util.ByteString
 import common.WskActorSystem
 import spray.json._
 import spray.json.DefaultJsonProtocol._
@@ -46,7 +47,8 @@
     with Matchers
     with ScalaFutures
     with BeforeAndAfterAll
-    with WskActorSystem {
+    with WskActorSystem
+    with DbUtils {
 
     override implicit val patienceConfig = PatienceConfig(timeout = 10.seconds, interval = 0.5.seconds)
 
@@ -176,4 +178,61 @@
                 assert(r === attachment)
         }
     }
+
+    it should "fail if group=true is used together with reduce=false" in {
+        intercept[IllegalArgumentException] {
+            Await.result(client.executeView("", "")(reduce = false, group = true), 15.seconds)
+        }
+    }
+
+    it should "check group Parameter on view-execution" in {
+        assume(config.dbProvider == "Cloudant" || config.dbProvider == "CouchDB")
+
+        val ids = List("some_doc_1", "some_doc_2", "some_doc_3", "some_doc_4", "some_doc_5")
+        val docs = Map(
+            ids(0) -> JsObject("key" -> JsString("a"), "value" -> JsNumber(1)),
+            ids(1) -> JsObject("key" -> JsString("a"), "value" -> JsNumber(2)),
+            ids(2) -> JsObject("key" -> JsString("b"), "value" -> JsNumber(3)),
+            ids(3) -> JsObject("key" -> JsString("b"), "value" -> JsNumber(4)),
+            ids(4) -> JsObject("key" -> JsString("c"), "value" -> JsNumber(5)))
+        val designDocName = "testDocument"
+        val viewName = "sumOfValues"
+        val designDoc = JsObject(
+            "views" -> JsObject(viewName -> JsObject(
+                "reduce" -> JsString("_sum"),
+                "map" -> JsString("function (doc) {\n  if(doc.key && doc.value) {\n    emit([doc.key], doc.value);\n  }\n}"))),
+            "language" -> JsString("javascript"))
+
+        Await.result(client.putDoc(s"_design/$designDocName", designDoc), 15.seconds)
+        docs.map {
+            case (id, doc) =>
+                Await.result(client.putDoc(id, doc), 15.seconds)
+        }
+
+        waitOnView(client, designDocName, viewName, docs.size)
+
+        val resultGroupedTrue = Await.result(client.executeView(designDocName, viewName)(reduce = true, group = true), 15.seconds)
+        resultGroupedTrue should be('right)
+        val jsObjectTrue = resultGroupedTrue.right.get
+        var rows = jsObjectTrue.fields("rows").convertTo[List[JsObject]]
+        rows.length should equal(3)
+        rows(0) shouldBe JsObject("key" -> JsArray(JsString("a")), "value" -> JsNumber(3))
+        rows(1) shouldBe JsObject("key" -> JsArray(JsString("b")), "value" -> JsNumber(7))
+        rows(2) shouldBe JsObject("key" -> JsArray(JsString("c")), "value" -> JsNumber(5))
+
+        val resultGroupedFalse = Await.result(client.executeView(designDocName, viewName)(reduce = true, group = false), 15.seconds)
+        resultGroupedFalse should be('right)
+        val jsObjectFalse = resultGroupedFalse.right.get
+        rows = jsObjectFalse.fields("rows").convertTo[List[JsObject]]
+        rows.length should equal(1)
+        rows(0).fields("value") should equal(JsNumber(15))
+
+        val resultGroupedWithout = Await.result(client.executeView(designDocName, viewName)(reduce = true), 15.seconds)
+        resultGroupedWithout should be('right)
+        val jsObjectWithout = resultGroupedWithout.right.get
+        rows = jsObjectWithout.fields("rows").convertTo[List[JsObject]]
+        rows.length should equal(1)
+        rows(0).fields("value") should equal(JsNumber(15))
+
+    }
 }
diff --git a/tests/src/whisk/core/database/test/DbUtils.scala b/tests/src/whisk/core/database/test/DbUtils.scala
index fddeddf..687f9fa 100644
--- a/tests/src/whisk/core/database/test/DbUtils.scala
+++ b/tests/src/whisk/core/database/test/DbUtils.scala
@@ -16,31 +16,36 @@
 
 package whisk.core.database.test
 
+import java.util.concurrent.TimeoutException
+
 import scala.collection.mutable.ListBuffer
 import scala.concurrent.Await
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
 import scala.concurrent.duration.Duration
 import scala.concurrent.duration.DurationInt
+import scala.language.postfixOps
 import scala.util.Failure
 import scala.util.Success
 import scala.util.Try
+
+import spray.json._
+import spray.json.DefaultJsonProtocol._
+import whisk.common.TransactionCounter
 import whisk.common.TransactionId
 import whisk.core.database.ArtifactStore
+import whisk.core.database.CouchDbRestClient
 import whisk.core.database.DocumentFactory
 import whisk.core.database.NoDocumentException
+import whisk.core.entity.DocId
 import whisk.core.entity.DocInfo
 import whisk.core.entity.EntityPath
 import whisk.core.entity.UUID
 import whisk.core.entity.WhiskAuth
+import whisk.core.entity.WhiskDocument
 import whisk.core.entity.WhiskEntityQueries
 import whisk.core.entity.types.AuthStore
 import whisk.core.entity.types.EntityStore
-import whisk.core.entity.WhiskDocument
-import whisk.core.entity.DocId
-import java.util.concurrent.TimeoutException
-import whisk.common.TransactionCounter
-import scala.language.postfixOps
 
 trait DbUtils extends TransactionCounter {
     implicit val dbOpTimeout = 15 seconds
@@ -127,6 +132,25 @@
     }
 
     /**
+     * Wait on view using the CouchDbRestClient. This is like the other waitOnViews.
+     */
+    def waitOnView(db: CouchDbRestClient, designDocName: String, viewName: String, count: Int)(
+        implicit context: ExecutionContext, timeout: Duration) = {
+        val success = retry(() => {
+            db.executeView(designDocName, viewName)().map {
+                case Right(doc) =>
+                    val length = doc.fields("rows").convertTo[List[JsObject]].length
+                    if (length != count) {
+                        throw RetryOp()
+                    } else true
+                case Left(_) =>
+                    throw RetryOp()
+            }
+        }, timeout)
+        assert(success.isSuccess, "wait aborted after: " + timeout + ": " + success)
+    }
+
+    /**
      * Puts document 'w' in datastore, and add it to gc queue to delete after the test completes.
      */
     def put[A, Au >: A](db: ArtifactStore[Au], w: A, garbageCollect: Boolean = true)(