You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cs...@apache.org on 2017/06/26 15:59:53 UTC

[incubator-openwhisk-cli] 13/36: API GW V2 - Update backend actions (#2067)

This is an automated email from the ASF dual-hosted git repository.

csantanapr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-cli.git

commit 774291e6bd74e32d135702c2b3b509e50f1cc66f
Author: Mark Deuser <md...@us.ibm.com>
AuthorDate: Fri Apr 7 08:49:27 2017 -0400

    API GW V2 - Update backend actions (#2067)
    
    * API GW V2 backend - action test updates
    
    * API GW V2 backend - ansible update
    - Add a new API GW V2 host property
    
    * API GW V2 backend - action updates
    - Dual support for existing "V1" API GW (to be deprecated) and new "V2" API GW
    - Create new V2 package for containing actions defined without "require-whisk-auth"
---
 .../actions/test/ApiGwRoutemgmtActionTests.scala   | 295 ++++++++++++++++++++-
 1 file changed, 286 insertions(+), 9 deletions(-)

diff --git a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
index d289d7a..96a1002 100644
--- a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
+++ b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
@@ -148,7 +148,7 @@ class ApiGwRoutemgmtActionTests
         return rr
     }
 
-    def apiMatch(
+    def apiMatchExperimental(
         apiarr: Vector[JsValue],
         basepath: String = "/",
         relpath: String = "",
@@ -173,7 +173,7 @@ class ApiGwRoutemgmtActionTests
                             if (endpointMatches) {
                                 System.out.println("endpoint exists/matches : " + relpath + "  " + operation)
                                 val actionConfig = JsObjectHelper(api.asJsObject).getFieldPath("value", "apidoc", "paths", relpath, operation, "x-ibm-op-ext").get.asJsObject
-                                val actionMatches = actionMatch(actionConfig, action)
+                                val actionMatches = actionMatchExperimental(actionConfig, action)
                                 if (actionMatches) {
                                     System.out.println("endpoint action matches")
                                     matches = true;
@@ -187,7 +187,7 @@ class ApiGwRoutemgmtActionTests
         return matches
     }
 
-    def actionMatch(
+    def actionMatchExperimental(
         jsAction: JsObject,
         action: ApiAction): Boolean = {
         val matches = jsAction.fields("backendMethod").convertTo[String] == action.backendMethod &&
@@ -197,6 +197,150 @@ class ApiGwRoutemgmtActionTests
         return matches
     }
 
+    def getApisV2(
+        bpOrName: Option[String],
+        relpath: Option[String] = None,
+        operation: Option[String] = None,
+        docid: Option[String] = None,
+        accesstoken: Option[String] = Some("AnAccessToken"),
+        spaceguid: Option[String] = Some("ASpaceGuid") ): Vector[JsValue] = {
+        val parms = Map[String, JsValue]() ++
+            Map("__ow_user" -> wskprops.namespace.toJson) ++
+            { bpOrName map { b => Map("basepath" -> b.toJson) } getOrElse Map[String, JsValue]() } ++
+            { relpath map { r => Map("relpath" -> r.toJson) } getOrElse Map[String, JsValue]() } ++
+            { operation map { o => Map("operation" -> o.toJson) } getOrElse Map[String, JsValue]() } ++
+            { docid map { d => Map("docid" -> d.toJson) } getOrElse Map[String, JsValue]() } ++
+            { accesstoken map { t => Map("accesstoken" -> t.toJson) } getOrElse Map[String, JsValue]() } ++
+            { spaceguid map { s => Map("spaceguid" -> s.toJson) } getOrElse Map[String, JsValue]() }
+
+        val rr = wsk.action.invoke(
+            name = "apimgmt/getApi",
+            parameters = parms,
+            blocking = true,
+            result = true,
+            expectedExitCode = SUCCESS_EXIT)(wskprops)
+        var apiJsArray: JsArray =
+            try {
+                var apisobj = rr.stdout.parseJson.asJsObject.fields("apis")
+                apisobj.convertTo[JsArray]
+            } catch {
+                case e: Exception =>
+                    JsArray.empty
+            }
+        return apiJsArray.elements
+    }
+
+    def createApiV2(
+        namespace: Option[String] = Some("_"),
+        basepath: Option[String] = Some("/"),
+        relpath: Option[String],
+        operation: Option[String],
+        apiname: Option[String],
+        action: Option[ApiAction],
+        swagger: Option[String] = None,
+        accesstoken: Option[String] = Some("AnAccessToken"),
+        spaceguid: Option[String] = Some("ASpaceGuid"),
+        expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
+        val parms = Map[String, JsValue]() ++
+            { namespace map { n => Map("namespace" -> n.toJson) } getOrElse Map[String, JsValue]() } ++
+            { basepath map { b => Map("gatewayBasePath" -> b.toJson) } getOrElse Map[String, JsValue]() } ++
+            { relpath map { r => Map("gatewayPath" -> r.toJson) } getOrElse Map[String, JsValue]() } ++
+            { operation map { o => Map("gatewayMethod" -> o.toJson) } getOrElse Map[String, JsValue]() } ++
+            { apiname map { an => Map("apiName" -> an.toJson) } getOrElse Map[String, JsValue]() } ++
+            { action map { a => Map("action" -> a.toJson) } getOrElse Map[String, JsValue]() } ++
+            { swagger map { s => Map("swagger" -> s.toJson) } getOrElse Map[String, JsValue]() }
+        val parm = Map[String, JsValue]("apidoc" -> JsObject(parms)) ++
+            { namespace map { n => Map("__ow_user" -> n.toJson) } getOrElse Map[String, JsValue]() } ++
+            { accesstoken map { t => Map("accesstoken" -> t.toJson) } getOrElse Map[String, JsValue]() } ++
+            { spaceguid map { s => Map("spaceguid" -> s.toJson) } getOrElse Map[String, JsValue]() }
+
+        val rr = wsk.action.invoke(
+            name = "apimgmt/createApi",
+            parameters = parm,
+            blocking = true,
+            result = true,
+            expectedExitCode = expectedExitCode)(wskprops)
+        return rr
+    }
+
+    def deleteApiV2(
+        namespace: Option[String] = Some("_"),
+        basepath: Option[String] = Some("/"),
+        relpath: Option[String] = None,
+        operation: Option[String] = None,
+        apiname: Option[String] = None,
+        accesstoken: Option[String] = Some("AnAccessToken"),
+        spaceguid: Option[String] = Some("ASpaceGuid"),
+        expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
+        val parms = Map[String, JsValue]() ++
+            { namespace map { n => Map("__ow_user" -> n.toJson) } getOrElse Map[String, JsValue]() } ++
+            { basepath map { b => Map("basepath" -> b.toJson) } getOrElse Map[String, JsValue]() } ++
+            { relpath map { r => Map("relpath" -> r.toJson) } getOrElse Map[String, JsValue]() } ++
+            { operation map { o => Map("operation" -> o.toJson) } getOrElse Map[String, JsValue]() } ++
+            { apiname map { an => Map("apiname" -> an.toJson) } getOrElse Map[String, JsValue]() } ++
+            { accesstoken map { t => Map("accesstoken" -> t.toJson) } getOrElse Map[String, JsValue]() } ++
+            { spaceguid map { s => Map("spaceguid" -> s.toJson) } getOrElse Map[String, JsValue]() }
+
+        val rr = wsk.action.invoke(
+            name = "apimgmt/deleteApi",
+            parameters = parms,
+            blocking = true,
+            result = true,
+            expectedExitCode = expectedExitCode)(wskprops)
+        return rr
+    }
+
+    def apiMatch(
+        apiarr: Vector[JsValue],
+        basepath: String = "/",
+        relpath: String = "",
+        operation: String = "",
+        apiname: String = "",
+        action: ApiAction = null): Boolean = {
+        var matches: Boolean = false
+        for (api <- apiarr) {
+            val basepathExists = JsObjectHelper(api.asJsObject).fieldPathExists("value", "apidoc", "basePath")
+            if (basepathExists) {
+                System.out.println("basePath exists")
+                val basepathMatches = (JsObjectHelper(api.asJsObject).getFieldPath("value", "apidoc", "basePath").get.convertTo[String] == basepath)
+                if (basepathMatches) {
+                    System.out.println("basePath matches: " + basepath)
+                    val apinameExists = JsObjectHelper(api.asJsObject).fieldPathExists("value", "apidoc", "info", "title")
+                    if (apinameExists) {
+                        System.out.println("api name exists")
+                        val apinameMatches = (JsObjectHelper(api.asJsObject).getFieldPath("value", "apidoc", "info", "title").get.convertTo[String] == apiname)
+                        if (apinameMatches) {
+                            System.out.println("api name matches: " + apiname)
+                            val endpointMatches = JsObjectHelper(api.asJsObject).fieldPathExists("value", "apidoc", "paths", relpath, operation)
+                            if (endpointMatches) {
+                                System.out.println("endpoint exists/matches : " + relpath + "  " + operation)
+                                val actionConfig = JsObjectHelper(api.asJsObject).getFieldPath("value", "apidoc", "paths", relpath, operation, "x-openwhisk").get.asJsObject
+                                val actionMatches = actionMatch(actionConfig, action)
+                                if (actionMatches) {
+                                    System.out.println("endpoint action matches")
+                                    matches = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return matches
+    }
+
+    def actionMatch(
+        jsAction: JsObject,
+        action: ApiAction): Boolean = {
+        System.out.println("actionMatch: url " + jsAction.fields("url").convertTo[String] + "; backendUrl " + action.backendUrl)
+        System.out.println("actionMatch: namespace " + jsAction.fields("namespace").convertTo[String] + "; namespace " + action.namespace)
+        System.out.println("actionMatch: action " + jsAction.fields("action").convertTo[String] + "; action " + action.name)
+        val matches = jsAction.fields("url").convertTo[String] == action.backendUrl &&
+            jsAction.fields("namespace").convertTo[String] == action.namespace &&
+            jsAction.fields("action").convertTo[String] == action.name
+        return matches
+    }
+
     behavior of "API Gateway routemgmt action parameter validation"
 
     it should "verify successful creation of a new API" in {
@@ -217,7 +361,7 @@ class ApiGwRoutemgmtActionTests
             JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
             val apiVector = getApis(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
             apiVector.size should be > 0
-            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+            apiMatchExperimental(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
         } finally {
             val deleteResult = deleteApi(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
         }
@@ -241,10 +385,10 @@ class ApiGwRoutemgmtActionTests
             JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
             var apiVector = getApis(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
             apiVector.size should be > 0
-            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+            apiMatchExperimental(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
             val deleteResult = deleteApi(namespace = Some(wskprops.namespace), basepath = Some(testbasepath))
             apiVector = getApis(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(false)
+            apiMatchExperimental(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(false)
         } finally {
             val deleteResult = deleteApi(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
         }
@@ -272,8 +416,8 @@ class ApiGwRoutemgmtActionTests
             JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
             var apiVector = getApis(bpOrName = Some(testbasepath))
             apiVector.size should be > 0
-            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
-            apiMatch(apiVector, testbasepath, testnewrelpath, testnewurlop, testapiname, testaction) should be(true)
+            apiMatchExperimental(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+            apiMatchExperimental(apiVector, testbasepath, testnewrelpath, testnewurlop, testapiname, testaction) should be(true)
         } finally {
             val deleteResult = deleteApi(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
         }
@@ -287,7 +431,7 @@ class ApiGwRoutemgmtActionTests
             //deleteApi
             ("/whisk.system/routemgmt/deleteApi", ANY_ERROR_EXIT, "namespace is required", Seq("-p", "basepath", "/ApiGwRoutemgmtActionTests_bp")),
             ("/whisk.system/routemgmt/deleteApi", ANY_ERROR_EXIT, "basepath is required", Seq("-p", "__ow_meta_namespace", "_")),
-            ("/whisk.system/routemgmt/deleteApi", ANY_ERROR_EXIT, "When specifying an operation, the relpath is required",
+            ("/whisk.system/routemgmt/deleteApi", ANY_ERROR_EXIT, "When specifying an operation, the path is required",
                 Seq("-p", "__ow_meta_namespace", "_", "-p", "basepath", "/ApiGwRoutemgmtActionTests_bp", "-p", "operation", "get")),
 
             //createApi
@@ -329,4 +473,137 @@ class ApiGwRoutemgmtActionTests
                 rr.stderr should include regex (errmsg)
         }
     }
+
+    behavior of "API Gateway apimgmt action parameter validation"
+
+    it should "verify successful creation of a new V2 API" in {
+        val testName = "APIGWTEST1"
+        val testbasepath = "/" + testName + "_bp"
+        val testrelpath = "/path"
+        val testurlop = "get"
+        val testapiname = testName + " API Name"
+        val actionName = testName + "_action"
+        val actionNamespace = wskprops.namespace
+        val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".http"
+        val actionAuthKey = testName + "_authkey"
+        val testaction = ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+
+        try {
+            val createResult = createApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), relpath = Some(testrelpath),
+                operation = Some(testurlop), apiname = Some(testapiname), action = Some(testaction))
+            JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
+            val apiVector = getApisV2(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            apiVector.size should be > 0
+            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+        } finally {
+            val deleteResult = deleteApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
+        }
+    }
+
+    it should "verify successful V2 API deletion using basepath" in {
+        val testName = "APIGWTEST2"
+        val testbasepath = "/" + testName + "_bp"
+        val testrelpath = "/path"
+        val testurlop = "get"
+        val testapiname = testName + " API Name"
+        val actionName = testName + "_action"
+        val actionNamespace = wskprops.namespace
+        val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".http"
+        val actionAuthKey = testName + "_authkey"
+        val testaction = ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+
+        try {
+            val createResult = createApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), relpath = Some(testrelpath),
+                operation = Some(testurlop), apiname = Some(testapiname), action = Some(testaction))
+            JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
+            var apiVector = getApisV2(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            apiVector.size should be > 0
+            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+            val deleteResult = deleteApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath))
+            apiVector = getApisV2(bpOrName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(false)
+        } finally {
+            val deleteResult = deleteApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
+        }
+    }
+
+    it should "verify successful addition of new relative path to existing V2 API" in {
+        val testName = "APIGWTEST3"
+        val testbasepath = "/" + testName + "_bp"
+        val testrelpath = "/path"
+        val testnewrelpath = "/path_new"
+        val testurlop = "get"
+        val testnewurlop = "delete"
+        val testapiname = testName + " API Name"
+        val actionName = testName + "_action"
+        val actionNamespace = wskprops.namespace
+        val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".http"
+        val actionAuthKey = testName + "_authkey"
+        val testaction = ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+
+        try {
+            var createResult = createApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), relpath = Some(testrelpath),
+                operation = Some(testurlop), apiname = Some(testapiname), action = Some(testaction))
+            createResult = createApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), relpath = Some(testnewrelpath),
+                operation = Some(testnewurlop), apiname = Some(testapiname), action = Some(testaction))
+            JsObjectHelper(createResult.stdout.parseJson.asJsObject).fieldPathExists("apidoc") should be(true)
+            var apiVector = getApisV2(bpOrName = Some(testbasepath))
+            apiVector.size should be > 0
+            apiMatch(apiVector, testbasepath, testrelpath, testurlop, testapiname, testaction) should be(true)
+            apiMatch(apiVector, testbasepath, testnewrelpath, testnewurlop, testapiname, testaction) should be(true)
+        } finally {
+            val deleteResult = deleteApiV2(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
+        }
+    }
+
+    it should "reject apimgmt actions that are invoked with not enough parameters" in {
+        val invalidArgs = Seq(
+            //getApi
+            ("/whisk.system/apimgmt/getApi", ANY_ERROR_EXIT, "namespace is required", Seq()),
+
+            //deleteApi
+            ("/whisk.system/apimgmt/deleteApi", ANY_ERROR_EXIT, "namespace is required", Seq("-p", "basepath", "/ApiGwRoutemgmtActionTests_bp")),
+            ("/whisk.system/apimgmt/deleteApi", ANY_ERROR_EXIT, "basepath is required", Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
+            ("/whisk.system/apimgmt/deleteApi", ANY_ERROR_EXIT, "When specifying an operation, the path is required",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "basepath", "/ApiGwRoutemgmtActionTests_bp", "-p", "operation", "get")),
+
+            //createApi
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is required", Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is missing the namespace field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{}")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is missing the gatewayBasePath field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_"}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is missing the gatewayPath field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp"}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is missing the gatewayMethod field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp"}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc is missing the action field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get"}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "action is missing the backendMethod field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "action is missing the backendUrl field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post"}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "action is missing the namespace field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL"}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "action is missing the name field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_"}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "action is missing the authkey field",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N"}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "swagger and gatewayBasePath are mutually exclusive and cannot be specified together",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N","authkey":"XXXX"},"swagger":{}}""")),
+            ("/whisk.system/apimgmt/createApi", ANY_ERROR_EXIT, "apidoc field cannot be parsed. Ensure it is valid JSON",
+              Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{1:[}}}")))
+
+        invalidArgs foreach {
+            case (action: String, exitcode: Int, errmsg: String, params: Seq[String]) =>
+                val cmd: Seq[String] = Seq("action",
+                    "invoke",
+                    action,
+                    "-i", "-b", "-r",
+                    "--apihost", wskprops.apihost,
+                    "--auth", wskprops.authKey) ++ params
+                val rr = wsk.cli(cmd, expectedExitCode = exitcode)
+                rr.stderr should include regex (errmsg)
+        }
+    }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>.