dubeejw commented on a change in pull request #2589: Add the fundamental 
framework of REST invocation for test cases
URL: 
https://github.com/apache/incubator-openwhisk/pull/2589#discussion_r145796086
 
 

 ##########
 File path: tests/src/test/scala/common/rest/WskRest.scala
 ##########
 @@ -0,0 +1,1434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common.rest
+
+import java.io.File
+import java.time.Clock
+import java.time.Instant
+import java.util.Base64
+
+import org.apache.commons.io.FileUtils
+import org.scalatest.Matchers
+import org.scalatest.FlatSpec
+import org.scalatest.concurrent.ScalaFutures
+import org.scalatest.time.Span.convertDurationToSpan
+
+import scala.Left
+import scala.Right
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.collection.mutable.Buffer
+import scala.collection.immutable.Seq
+import scala.concurrent.duration.Duration
+import scala.concurrent.duration.DurationInt
+import scala.concurrent.Future
+import scala.language.postfixOps
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+import scala.util.{Failure, Success}
+
+import akka.http.scaladsl.model.StatusCode
+import akka.http.scaladsl.model.StatusCodes.Accepted
+import akka.http.scaladsl.model.StatusCodes.NotFound
+import akka.http.scaladsl.model.StatusCodes.BadRequest
+import akka.http.scaladsl.model.StatusCodes.OK
+import akka.http.scaladsl.model.HttpRequest
+import akka.http.scaladsl.model.HttpMethod
+import akka.http.scaladsl.model.HttpResponse
+import akka.http.scaladsl.model.headers.Authorization
+import akka.http.scaladsl.model.HttpEntity
+import akka.http.scaladsl.model.ContentTypes
+import akka.http.scaladsl.Http
+
+import akka.http.scaladsl.model.headers.BasicHttpCredentials
+import akka.http.scaladsl.model.Uri
+import akka.http.scaladsl.model.Uri.Path
+
+import akka.http.scaladsl.model.HttpMethods.DELETE
+import akka.http.scaladsl.model.HttpMethods.GET
+import akka.http.scaladsl.model.HttpMethods.POST
+import akka.http.scaladsl.model.HttpMethods.PUT
+
+import akka.stream.ActorMaterializer
+
+import spray.json._
+import spray.json.DefaultJsonProtocol._
+import spray.json.JsObject
+import spray.json.JsValue
+import spray.json.pimpString
+
+import common._
+import common.BaseDeleteFromCollection
+import common.BaseListOrGetFromCollection
+import common.HasActivation
+import common.RunWskCmd
+import common.TestUtils
+import common.TestUtils.SUCCESS_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.ANY_ERROR_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.RunResult
+import common.WaitFor
+import common.WhiskProperties
+import common.WskActorSystem
+import common.WskProps
+
+import whisk.core.entity.ByteSize
+import whisk.utils.retry
+
+class WskRest() extends RunWskRestCmd with BaseWsk {
+  override implicit val action = new WskRestAction
+  override implicit val trigger = new WskRestTrigger
+  override implicit val rule = new WskRestRule
+  override implicit val activation = new WskRestActivation
+  override implicit val pkg = new WskRestPackage
+  override implicit val namespace = new WskRestNamespace
+  override implicit val api = new WskRestApi
+}
+
+trait ListOrGetFromCollectionRest extends BaseListOrGetFromCollection {
+  self: RunWskRestCmd =>
+
+  /**
+   * List entities in collection.
+   *
+   * @param namespace (optional) if specified must be  fully qualified 
namespace
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def list(namespace: Option[String] = None,
+                    limit: Option[Int] = None,
+                    nameSort: Option[Boolean] = None,
+                    expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+    val (ns, name) = getNamespaceActionName(resolve(namespace))
+    val pathToList = s"$basePath/namespaces/$ns/$noun"
+    val entPath =
+      if (name != "") Path(s"$pathToList/$name/")
+      else Path(s"$pathToList")
+    val paramMap = Map[String, String]() ++ { Map("skip" -> "0", "docs" -> 
true.toString) } ++ {
+      limit map { l =>
+        Map("limit" -> l.toString)
+      } getOrElse Map[String, String]("limit" -> "30")
+    }
+    val resp = requestEntity(GET, entPath, paramMap)
+    val r = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, r.statusCode.intValue)
+    r
+  }
+
+  /**
+   * Gets entity from collection.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def get(name: String,
+                   expectedExitCode: Int = OK.intValue,
+                   summary: Boolean = false,
+                   fieldFilter: Option[String] = None,
+                   url: Option[Boolean] = None,
+                   save: Option[Boolean] = None,
+                   saveAs: Option[String] = None)(implicit wp: WskProps): 
RestResult = {
+    val (ns, entity) = getNamespaceActionName(name)
+    val entPath = Path(s"$basePath/namespaces/$ns/$noun/$entity")
+    val resp = requestEntity(GET, entPath)(wp)
+    val r = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, r.statusCode.intValue)
+    r
+  }
+}
+
+trait DeleteFromCollectionRest extends BaseDeleteFromCollection {
+  self: RunWskRestCmd =>
+
+  /**
+   * Deletes entity from collection.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def delete(entity: String, expectedExitCode: Int = 
OK.intValue)(implicit wp: WskProps): RestResult = {
+    val (ns, entityName) = getNamespaceActionName(entity)
+    val path = Path(s"$basePath/namespaces/$ns/$noun/$entityName")
+    val resp = requestEntity(DELETE, path)(wp)
+    val r = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, r.statusCode.intValue)
+    r
+  }
+
+  /**
+   * Deletes entity from collection but does not assert that the command 
succeeds.
+   * Use this if deleting an entity that may not exist and it is OK if it does 
not.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   */
+  override def sanitize(name: String)(implicit wp: WskProps): RestResult = {
+    delete(name, DONTCARE_EXIT)
+  }
+}
+
+trait HasActivationRest extends HasActivation {
+
+  /**
+   * Extracts activation id from invoke (action or trigger) or activation get
+   */
+  override def extractActivationId(result: RunResult): Option[String] = {
+    extractActivationIdFromInvoke(result.asInstanceOf[RestResult])
+  }
+
+  /**
+   * Extracts activation id from 'wsk action invoke' or 'wsk trigger invoke'
+   */
+  private def extractActivationIdFromInvoke(result: RestResult): 
Option[String] = {
+    Try {
+      val activationID =
+        if ((result.statusCode == OK) || (result.statusCode == Accepted)) 
result.getField("activationId") else ""
+      activationID
+    } toOption
+  }
+}
+
+class WskRestAction
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with HasActivationRest
+    with BaseAction {
+
+  override protected val noun = "actions"
+
+  /**
+   * Creates action. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(
+    name: String,
+    artifact: Option[String],
+    kind: Option[String] = None, // one of docker, copy, sequence or none for 
autoselect else an explicit type
+    main: Option[String] = None,
+    docker: Option[String] = None,
+    parameters: Map[String, JsValue] = Map(),
+    annotations: Map[String, JsValue] = Map(),
+    parameterFile: Option[String] = None,
+    annotationFile: Option[String] = None,
+    timeout: Option[Duration] = None,
+    memory: Option[ByteSize] = None,
+    logsize: Option[ByteSize] = None,
+    shared: Option[Boolean] = None,
+    update: Boolean = false,
+    web: Option[String] = None,
+    expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
+
+    val (namespace, actName) = getNamespaceActionName(name)
+    var exec = Map[String, JsValue]()
+    var code = ""
+    var kindType = ""
+    var artifactName = ""
+    artifact match {
+      case Some(artifactFile) => {
+        val ext = getExt(artifactFile)
+        artifactName = artifactFile
+        ext match {
+          case ".jar" => {
+            kindType = "java:default"
+            val jar = FileUtils.readFileToByteArray(new File(artifactFile))
+            code = Base64.getEncoder.encodeToString(jar)
+          }
+          case ".zip" => {
+            val zip = FileUtils.readFileToByteArray(new File(artifactFile))
+            code = Base64.getEncoder.encodeToString(zip)
+          }
+          case ".js" => {
+            kindType = "nodejs:default"
+            code = FileUtils.readFileToString(new File(artifactFile))
+          }
+          case ".py" => {
+            kindType = "python:default"
+            code = FileUtils.readFileToString(new File(artifactFile))
+          }
+          case ".swift" => {
+            kindType = "swift:default"
+            code = FileUtils.readFileToString(new File(artifactFile))
+          }
+          case ".php" => {
+            kindType = "php:default"
+            code = FileUtils.readFileToString(new File(artifactFile))
+          }
+          case _ =>
+        }
+      }
+      case None =>
+    }
+
+    kindType = docker map { d =>
+      "blackbox"
+    } getOrElse kindType
+
+    var (params, annos) = getParamsAnnos(parameters, annotations, 
parameterFile, annotationFile, web = web)
+
+    kind match {
+      case Some(k) => {
+        k match {
+          case "copy" => {
+            val actName = entityName(artifactName)
+            val actionPath = 
Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+            val resp = requestEntity(GET, actionPath)
+            if (resp == None)
+              return new RestResult(NotFound)
+            else {
+              val result = new RestResult(resp.status, getRespData(resp))
+              params = JsArray(result.getFieldListJsObject("parameters"))
+              annos = JsArray(result.getFieldListJsObject("annotations"))
+              exec = result.getFieldJsObject("exec").fields
+            }
+          }
+          case "sequence" => {
+            val comps = convertIntoComponents(artifactName)
+            exec = Map("components" -> comps.toJson, "kind" -> k.toJson)
+          }
+          case "native" => {
+            exec = Map("code" -> code.toJson, "kind" -> "blackbox".toJson, 
"image" -> "openwhisk/dockerskeleton".toJson)
+          }
+          case _ => {
+            exec = Map("code" -> code.toJson, "kind" -> k.toJson)
+          }
+        }
+      }
+      case None => exec = Map("code" -> code.toJson, "kind" -> kindType.toJson)
+    }
+
+    exec = exec ++ {
+      main map { m =>
+        Map("main" -> m.toJson)
+      } getOrElse Map[String, JsValue]()
+    } ++ {
+      docker map { d =>
+        Map("kind" -> "blackbox".toJson, "image" -> d.toJson)
+      } getOrElse Map[String, JsValue]()
+    }
+
+    var bodyContent = Map("name" -> name.toJson, "namespace" -> 
namespace.toJson)
+
+    if (update) {
+      kind match {
+        case Some(k) if (k == "sequence" || k == "native") => {
+          bodyContent = bodyContent ++ Map("exec" -> exec.toJson)
+        }
+      }
+
+      bodyContent = bodyContent ++ {
+        shared map { s =>
+          Map("publish" -> s.toJson)
+        } getOrElse Map[String, JsValue]()
+      }
+
+      val inputParams = convertMapIntoKeyValue(parameters)
+      if (inputParams.elements.size > 0) {
+        bodyContent = bodyContent ++ Map("parameters" -> params)
+      }
+      val inputAnnos = convertMapIntoKeyValue(annotations)
+      if (inputAnnos.elements.size > 0) {
+        bodyContent = bodyContent ++ Map("annotations" -> annos)
+      }
+    } else {
+      bodyContent = bodyContent ++ Map("exec" -> exec.toJson, "parameters" -> 
params, "annotations" -> annos)
+
+      bodyContent = bodyContent ++ {
+        timeout map { t =>
+          Map("limits" -> JsObject("timeout" -> t.toMillis.toJson))
+        } getOrElse Map[String, JsValue]()
+      }
+    }
+
+    val path = Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+    val resp =
+      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), 
Some(JsObject(bodyContent).toString))
+      else requestEntity(PUT, path, body = 
Some(JsObject(bodyContent).toString))
+    val r = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, r.statusCode.intValue)
+    r
+  }
+
+  override def invoke(name: String,
+                      parameters: Map[String, JsValue] = Map(),
+                      parameterFile: Option[String] = None,
+                      blocking: Boolean = false,
+                      result: Boolean = false,
+                      expectedExitCode: Int = Accepted.intValue)(implicit wp: 
WskProps): RestResult = {
+    super.invokeAction(name, parameters, parameterFile, blocking, result, 
expectedExitCode = expectedExitCode)
+  }
+}
+
+class WskRestTrigger
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with HasActivationRest
+    with BaseTrigger {
+
+  override protected val noun = "triggers"
+
+  /**
+   * Creates trigger. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(name: String,
+                      parameters: Map[String, JsValue] = Map(),
+                      annotations: Map[String, JsValue] = Map(),
+                      parameterFile: Option[String] = None,
+                      annotationFile: Option[String] = None,
+                      feed: Option[String] = None,
+                      shared: Option[Boolean] = None,
+                      update: Boolean = false,
+                      expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+
+    val (ns, triggerName) = this.getNamespaceActionName(name)
+    val path = Path(s"$basePath/namespaces/$ns/$noun/$triggerName")
+    val (params, annos) = getParamsAnnos(parameters, annotations, 
parameterFile, annotationFile, feed)
+    var bodyContent = JsObject("name" -> name.toJson, "namespace" -> 
s"$ns".toJson)
+
+    if (!update) {
+      val published = shared map { s =>
+        s
+      } getOrElse false
+      bodyContent = JsObject(
+        bodyContent.fields + ("publish" -> published.toJson,
+        "parameters" -> params, "annotations" -> annos))
+    } else {
+      bodyContent = shared map { s =>
+        JsObject(bodyContent.fields + ("publish" -> s.toJson))
+      } getOrElse bodyContent
+
+      val inputParams = convertMapIntoKeyValue(parameters)
+      if (inputParams.elements.size > 0) {
+        bodyContent = JsObject(bodyContent.fields + ("parameters" -> params))
+      }
+      val inputAnnos = convertMapIntoKeyValue(annotations)
+      if (inputAnnos.elements.size > 0) {
+        bodyContent = JsObject(bodyContent.fields + ("annotations" -> annos))
+      }
+    }
+
+    val resp =
+      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), 
Some(bodyContent.toString))
+      else requestEntity(PUT, path, body = Some(bodyContent.toString))
+    val result = new RestResult(resp.status, getRespData(resp))
+    if (result.statusCode != OK) {
+      validateStatusCode(expectedExitCode, result.statusCode.intValue)
+      result
+    }
+    val r = feed map { f =>
+      // Invoke the feed
+      val (nsFeed, feedName) = this.getNamespaceActionName(f)
+      val path = Path(s"$basePath/namespaces/$nsFeed/actions/$feedName")
+      val paramMap = Map("blocking" -> "true".toString, "result" -> 
"false".toString)
+      var body: Map[String, JsValue] = Map(
+        "lifecycleEvent" -> "CREATE".toJson,
+        "triggerName" -> s"/$ns/$triggerName".toJson,
+        "authKey" -> s"${getAuthKey(wp)}".toJson)
+      body = body ++ parameters
+      val resp = requestEntity(POST, path, paramMap, 
Some(body.toJson.toString()))
+      val resultInvoke = new RestResult(resp.status, getRespData(resp))
+      expectedExitCode shouldBe resultInvoke.statusCode.intValue
+      if (resultInvoke.statusCode != OK) {
+        // Remove the trigger, because the feed failed to invoke.
+        this.delete(triggerName)
+      } else {
+        result
+      }
+    } getOrElse {
+      validateStatusCode(expectedExitCode, result.statusCode.intValue)
+      result
+    }
+    r
+  }
+
+  /**
+   * Fires trigger. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def fire(name: String,
+                    parameters: Map[String, JsValue] = Map(),
+                    parameterFile: Option[String] = None,
+                    expectedExitCode: Int = OK.intValue)(implicit wp: 
WskProps): RestResult = {
+    val path = getNamePath(noun, name)
+    val params = parameterFile map { l =>
+      val input = FileUtils.readFileToString(new File(l))
+      input.parseJson.convertTo[Map[String, JsValue]]
+    } getOrElse parameters
+    val resp =
+      if (params.size == 0) requestEntity(POST, path)
+      else requestEntity(POST, path, body = Some(params.toJson.toString()))
+    new RestResult(resp.status.intValue, getRespData(resp))
+  }
+}
+
+class WskRestRule
+    extends RunWskRestCmd
+    with ListOrGetFromCollectionRest
+    with DeleteFromCollectionRest
+    with WaitFor
+    with BaseRule {
+
+  override protected val noun = "rules"
+
+  /**
+   * Creates rule. Parameters mirror those available in the REST.
+   *
+   * @param name either a fully qualified name or a simple entity name
+   * @param trigger must be a simple name
+   * @param action must be a simple name
+   * @param expectedExitCode (optional) the expected exit code for the command
+   * if the code is anything but DONTCARE_EXIT, assert the code is as expected
+   */
+  override def create(name: String,
+                      trigger: String,
+                      action: String,
+                      annotations: Map[String, JsValue] = Map(),
+                      shared: Option[Boolean] = None,
+                      update: Boolean = false,
+                      expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: 
WskProps): RestResult = {
+    val path = getNamePath(noun, name)
+    val annos = convertMapIntoKeyValue(annotations)
+    val published = shared map { s =>
 
 Review comment:
   Here you can do `shared.getOrElse(false)`.

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to