openwhisk-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From csantan...@apache.org
Subject [incubator-openwhisk] branch master updated: Replace test cases of api gateway with REST (#2944)
Date Sat, 11 Nov 2017 13:21:28 GMT
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.git


The following commit(s) were added to refs/heads/master by this push:
     new c5e12ce  Replace test cases of api gateway with REST (#2944)
c5e12ce is described below

commit c5e12ce8b2ffdc10a4c418eb20c2dd0255bb5981
Author: Vincent <shou@us.ibm.com>
AuthorDate: Sat Nov 11 08:21:26 2017 -0500

    Replace test cases of api gateway with REST (#2944)
---
 .../apigw/healthtests/ApiGwCliEndToEndTests.scala  |  33 ++
 .../apigw/healthtests/ApiGwEndToEndTests.scala     |  73 ++-
 .../apigw/healthtests/ApiGwRestEndToEndTests.scala |  93 +++
 tests/src/test/scala/common/rest/WskRest.scala     |  97 ++--
 .../test/ApiGwCliRoutemgmtActionTests.scala        |  28 +
 .../test/ApiGwRestRoutemgmtActionTests.scala       |  31 +
 .../actions/test/ApiGwRoutemgmtActionTests.scala   | 227 +-------
 .../scala/whisk/core/cli/test/ApiGwCliTests.scala  |  33 ++
 .../scala/whisk/core/cli/test/ApiGwRestTests.scala | 248 ++++++++
 .../scala/whisk/core/cli/test/ApiGwTests.scala     | 641 +++++++--------------
 .../scala/whisk/core/cli/test/BaseApiGwTests.scala | 165 ++++++
 .../whisk/core/cli/test/WskCliApiGwTests.scala     | 438 ++++++++++++++
 12 files changed, 1407 insertions(+), 700 deletions(-)

diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala
new file mode 100644
index 0000000..3025fab
--- /dev/null
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala
@@ -0,0 +1,33 @@
+/*
+ * 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 apigw.healthtests
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+import common.TestUtils._
+
+/**
+ * Basic tests of the download link for Go CLI binaries
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliEndToEndTests extends ApiGwEndToEndTests {
+  override lazy val wsk: common.Wsk = new Wsk
+  override val createCode: Int = SUCCESS_EXIT
+}
diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
index d0dbe92..8995c7a 100644
--- a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
@@ -34,7 +34,7 @@ import com.jayway.restassured.RestAssured
 import common.TestHelpers
 import common.TestUtils
 import common.TestUtils._
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
@@ -45,7 +45,7 @@ import system.rest.RestUtil
  * Basic tests of the download link for Go CLI binaries
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwEndToEndTests
+abstract class ApiGwEndToEndTests
     extends FlatSpec
     with Matchers
     with RestUtil
@@ -53,23 +53,58 @@ class ApiGwEndToEndTests
     with WskTestHelpers
     with BeforeAndAfterAll {
 
-  implicit val wskprops = WskProps()
-  val wsk = new Wsk
-  val clinamespace = wsk.namespace.whois()
+  implicit val wskprops: common.WskProps = WskProps()
+  val wsk: BaseWsk
+  val namespace: String = wsk.namespace.whois()
+  val createCode: Int
 
   // Custom CLI properties file
-  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+  val cliWskPropsFile: java.io.File = File.createTempFile("wskprops", ".tmp")
 
   /*
    * Create a CLI properties file for use by the tests
    */
-  override def beforeAll() = {
+  override def beforeAll: Unit = {
     cliWskPropsFile.deleteOnExit()
     val wskprops = WskProps(token = "SOME TOKEN")
     wskprops.writeFile(cliWskPropsFile)
     println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
   }
 
+  def verifyAPICreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+    val apiurl = rr.stdout.split("\n")(1)
+    println(s"apiurl: '$apiurl'")
+  }
+
+  def verifyAPIList(rr: RunResult,
+                    actionName: String,
+                    testurlop: String,
+                    testapiname: String,
+                    testbasepath: String,
+                    testrelpath: String): Unit = {
+    rr.stdout should include("ok: APIs")
+    rr.stdout should include regex (s"$actionName\\s+$testurlop\\s+$testapiname\\s+")
+    rr.stdout should include(testbasepath + testrelpath)
+  }
+
+  def verifyAPISwaggerCreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+  }
+
+  def writeSwaggerFile(rr: RunResult): File = {
+    val swaggerfile = File.createTempFile("api", ".json")
+    swaggerfile.deleteOnExit()
+    val bw = new BufferedWriter(new FileWriter(swaggerfile))
+    bw.write(rr.stdout)
+    bw.close()
+    return swaggerfile
+  }
+
+  def getSwaggerApiUrl(rr: RunResult): String = {
+    return rr.stdout.split("\n")(1)
+  }
+
   behavior of "Wsk api"
 
   it should s"create an API and successfully invoke that API" in {
@@ -83,7 +118,7 @@ class ApiGwEndToEndTests
     val urlqueryvalue = testName
 
     try {
-      println("cli namespace: " + clinamespace)
+      println("Namespace: " + namespace)
 
       // Delete any lingering stale api from previous run that may not have been deleted properly
       wsk.api.delete(
@@ -93,12 +128,14 @@ class ApiGwEndToEndTests
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+      println("action creation Namespace: " + namespace)
       wsk.action.create(
         name = actionName,
         artifact = Some(file),
-        expectedExitCode = SUCCESS_EXIT,
+        expectedExitCode = createCode,
         annotations = Map("web-export" -> true.toJson))
 
+      println("creation Namespace: " + namespace)
       // Create the API
       var rr = wsk.api.create(
         basepath = Some(testbasepath),
@@ -108,9 +145,7 @@ class ApiGwEndToEndTests
         apiname = Some(testapiname),
         responsetype = Some("http"),
         cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: created API")
-      val apiurl = rr.stdout.split("\n")(1)
-      println(s"apiurl: '$apiurl'")
+      verifyAPICreated(rr)
 
       // Validate the API was successfully created
       // List result will look like:
@@ -122,17 +157,11 @@ class ApiGwEndToEndTests
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"$actionName\\s+$testurlop\\s+$testapiname\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyAPIList(rr, actionName, testurlop, testapiname, testbasepath, testrelpath)
 
       // Recreate the API using a JSON swagger file
       rr = wsk.api.get(basepathOrApiName = Some(testbasepath), cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      val swaggerfile = File.createTempFile("api", ".json")
-      swaggerfile.deleteOnExit()
-      val bw = new BufferedWriter(new FileWriter(swaggerfile))
-      bw.write(rr.stdout)
-      bw.close()
+      val swaggerfile = writeSwaggerFile(rr)
 
       // Delete API to that it can be recreated again using the generated swagger file
       val deleteApiResult = wsk.api.delete(
@@ -143,8 +172,8 @@ class ApiGwEndToEndTests
       // Create the API again, but use the swagger file this time
       rr = wsk.api
         .create(swagger = Some(swaggerfile.getAbsolutePath()), cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: created API")
-      val swaggerapiurl = rr.stdout.split("\n")(1)
+      verifyAPISwaggerCreated(rr)
+      val swaggerapiurl = getSwaggerApiUrl(rr)
       println(s"Returned api url: '${swaggerapiurl}'")
 
       // Call the API URL and validate the results
diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwRestEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwRestEndToEndTests.scala
new file mode 100644
index 0000000..279da3c
--- /dev/null
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwRestEndToEndTests.scala
@@ -0,0 +1,93 @@
+/*
+ * 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 apigw.healthtests
+
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+
+import akka.http.scaladsl.model.StatusCodes.OK
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.TestUtils._
+import common.rest.WskRest
+import common.rest.RestResult
+
+@RunWith(classOf[JUnitRunner])
+class ApiGwRestEndToEndTests extends ApiGwEndToEndTests {
+
+  override lazy val wsk: common.rest.WskRest = new WskRest
+  override val createCode: Int = OK.intValue
+
+  override def verifyAPICreated(rr: RunResult): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    apiResultRest.statusCode shouldBe OK
+    val apiurl = apiResultRest.getField("gwApiUrl") + "/path"
+    println(s"apiurl: '$apiurl'")
+  }
+
+  override def verifyAPIList(rr: RunResult,
+                             actionName: String,
+                             testurlop: String,
+                             testapiname: String,
+                             testbasepath: String,
+                             testrelpath: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    val basepath = RestResult.getField(apidoc, "basePath")
+    basepath shouldBe testbasepath
+
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    paths.fields.contains(testrelpath) shouldBe true
+
+    val info = RestResult.getFieldJsObject(apidoc, "info")
+    val title = RestResult.getField(info, "title")
+    title shouldBe testapiname
+
+    val relpath = RestResult.getFieldJsObject(paths, testrelpath)
+    val urlop = RestResult.getFieldJsObject(relpath, testurlop)
+    val openwhisk = RestResult.getFieldJsObject(urlop, "x-openwhisk")
+    val actionN = RestResult.getField(openwhisk, "action")
+    actionN shouldBe actionName
+  }
+
+  override def verifyAPISwaggerCreated(rr: RunResult): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    apiResultRest.statusCode shouldBe OK
+  }
+
+  override def writeSwaggerFile(rr: RunResult): File = {
+    val swaggerfile = File.createTempFile("api", ".json")
+    swaggerfile.deleteOnExit()
+    val bw = new BufferedWriter(new FileWriter(swaggerfile))
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    bw.write(apidoc.toString())
+    bw.close()
+    return swaggerfile
+  }
+
+  override def getSwaggerApiUrl(rr: RunResult): String = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    return apiResultRest.getField("gwApiUrl") + "/path"
+  }
+}
diff --git a/tests/src/test/scala/common/rest/WskRest.scala b/tests/src/test/scala/common/rest/WskRest.scala
index 71551a6..0ae28f9 100644
--- a/tests/src/test/scala/common/rest/WskRest.scala
+++ b/tests/src/test/scala/common/rest/WskRest.scala
@@ -190,8 +190,8 @@ trait DeleteFromCollectionRest extends BaseDeleteFromCollection {
    * @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) = getNamespaceEntityName(entity)
+  override def delete(name: String, expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
+    val (ns, entityName) = getNamespaceEntityName(name)
     val path = Path(s"$basePath/namespaces/$ns/$noun/$entityName")
     val resp = requestEntity(DELETE, path)(wp)
     val r = new RestResult(resp.status, getRespData(resp))
@@ -482,7 +482,7 @@ class WskRestTrigger
       var body: Map[String, JsValue] = Map(
         "lifecycleEvent" -> "CREATE".toJson,
         "triggerName" -> s"/$ns/$triggerName".toJson,
-        "authKey" -> s"${getAuthKey(wp)}".toJson)
+        "authKey" -> s"${wp.authKey}".toJson)
       body = body ++ parameters
       val resp = requestEntity(POST, path, paramMap, Some(body.toJson.toString()))
       val resultInvoke = new RestResult(resp.status, getRespData(resp))
@@ -971,11 +971,10 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
     val r = action match {
       case Some(action) => {
         val (ns, actionName) = this.getNamespaceEntityName(action)
-        val actionUrl = s"${WhiskProperties.getApiHostForAction}/$basePath/web/$ns/default/$actionName.http"
-        val actionAuthKey = this.getAuthKey(wp)
+        val actionUrl = s"${WhiskProperties.getApiHostForAction}$basePath/web/$ns/default/$actionName.http"
+        val actionAuthKey = wp.authKey
         val testaction = Some(
-          ApiAction(name = actionName, namespace = ns, backendUrl = actionUrl, authkey = actionAuthKey))
-
+          new ApiAction(name = actionName, namespace = ns, backendUrl = actionUrl, authkey = actionAuthKey))
         val parms = Map[String, JsValue]() ++ { Map("namespace" -> ns.toJson) } ++ {
           basepath map { b =>
             Map("gatewayBasePath" -> b.toJson)
@@ -1003,14 +1002,16 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
           } getOrElse Map[String, JsValue]()
         }
 
-        val parm = Map[String, JsValue]("apidoc" -> JsObject(parms)) ++ { Map("__ow_user" -> ns.toJson) } ++ {
+        val spaceguid = if (wp.authKey.contains(":")) wp.authKey.split(":")(0) else wp.authKey
+
+        val parm = Map[String, JsValue]("apidoc" -> JsObject(parms)) ++ {
           responsetype map { r =>
             Map("responsetype" -> r.toJson)
           } getOrElse Map[String, JsValue]()
         } ++ {
           Map("accesstoken" -> wp.authKey.toJson)
         } ++ {
-          Map("spaceguid" -> wp.authKey.split(":")(0).toJson)
+          Map("spaceguid" -> spaceguid.toJson)
         }
 
         invokeAction(
@@ -1018,10 +1019,44 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
           parameters = parm,
           blocking = true,
           result = true,
+          web = true,
           expectedExitCode = expectedExitCode)(wp)
       }
       case None => {
-        new RestResult(NotFound)
+        swagger match {
+          case Some(swaggerFile) => {
+            var file = ""
+            val fileName = swaggerFile.toString()
+            try {
+              file = FileUtils.readFileToString(new File(fileName))
+            } catch {
+              case e: Throwable =>
+                return new RestResult(
+                  NotFound,
+                  JsObject("error" -> s"Error reading swagger file '$fileName'".toJson).toString())
+            }
+            val parms = Map("namespace" -> s"${wp.namespace}".toJson, "swagger" -> file.toJson)
+            val parm = Map[String, JsValue]("apidoc" -> JsObject(parms)) ++ {
+              responsetype map { r =>
+                Map("responsetype" -> r.toJson)
+              } getOrElse Map[String, JsValue]()
+            } ++ {
+              Map("accesstoken" -> wp.authKey.toJson)
+            } ++ {
+              Map("spaceguid" -> wp.authKey.split(":")(0).toJson)
+            }
+            invokeAction(
+              name = "apimgmt/createApi",
+              parameters = parm,
+              blocking = true,
+              result = true,
+              web = true,
+              expectedExitCode = expectedExitCode)(wp)
+          }
+          case None => {
+            new RestResult(NotFound)
+          }
+        }
       }
     }
     r
@@ -1043,8 +1078,7 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
                     expectedExitCode: Int = SUCCESS_EXIT,
                     cliCfgFile: Option[String] = None)(implicit wp: WskProps): RestResult = {
 
-    val parms = Map[String, JsValue]() ++
-      Map("__ow_user" -> wp.namespace.toJson) ++ {
+    val parms = Map[String, JsValue]() ++ {
       basepathOrApiName map { b =>
         Map("basepath" -> b.toJson)
       } getOrElse Map[String, JsValue]()
@@ -1061,11 +1095,13 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
     } ++ {
       Map("spaceguid" -> wp.authKey.split(":")(0).toJson)
     }
+
     val rr = invokeAction(
       name = "apimgmt/getApi",
       parameters = parms,
       blocking = true,
       result = true,
+      web = true,
       expectedExitCode = OK.intValue)(wp)
     rr
   }
@@ -1082,8 +1118,7 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
                    expectedExitCode: Int = SUCCESS_EXIT,
                    cliCfgFile: Option[String] = None,
                    format: Option[String] = None)(implicit wp: WskProps): RestResult = {
-    val parms = Map[String, JsValue]() ++
-      Map("__ow_user" -> wp.namespace.toJson) ++ {
+    val parms = Map[String, JsValue]() ++ {
       basepathOrApiName map { b =>
         Map("basepath" -> b.toJson)
       } getOrElse Map[String, JsValue]()
@@ -1098,6 +1133,7 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
       parameters = parms,
       blocking = true,
       result = true,
+      web = true,
       expectedExitCode = OK.intValue)(wp)
     result
   }
@@ -1113,7 +1149,7 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
                       operation: Option[String] = None,
                       expectedExitCode: Int = SUCCESS_EXIT,
                       cliCfgFile: Option[String] = None)(implicit wp: WskProps): RestResult = {
-    val parms = Map[String, JsValue]() ++ { Map("__ow_user" -> wp.namespace.toJson) } ++ {
+    val parms = Map[String, JsValue]() ++ {
       Map("basepath" -> basepathOrApiName.toJson)
     } ++ {
       relpath map { r =>
@@ -1134,18 +1170,10 @@ class WskRestApi extends RunWskRestCmd with BaseApi {
       parameters = parms,
       blocking = true,
       result = true,
+      web = true,
       expectedExitCode = expectedExitCode)(wp)
     return rr
   }
-
-  def getApi(basepathOrApiName: String, params: Map[String, String] = Map(), expectedExitCode: Int = OK.intValue)(
-    implicit wp: WskProps): RestResult = {
-    val whiskUrl = Uri(WhiskProperties.getApiHostForAction)
-    val path = Path(s"/api/${wp.authKey.split(":")(0)}$basepathOrApiName/path")
-    val resp = requestEntity(GET, path, params, whiskUrl = whiskUrl)
-    val result = new RestResult(resp.status, getRespData(resp))
-    result
-  }
 }
 
 class RunWskRestCmd() extends FlatSpec with RunWskCmd with Matchers with ScalaFutures with WskActorSystem {
@@ -1155,6 +1183,7 @@ class RunWskRestCmd() extends FlatSpec with RunWskCmd with Matchers with ScalaFu
   val queueSize = 10
   val maxOpenRequest = 1024
   val basePath = Path("/api/v1")
+  val systemNamespace = "whisk.system"
 
   val sslConfig = AkkaSSLConfig().mapSettings { s =>
     s.withHostnameVerifierClass(classOf[AcceptAllHostNameVerifier].asInstanceOf[Class[HostnameVerifier]])
@@ -1239,11 +1268,6 @@ class RunWskRestCmd() extends FlatSpec with RunWskCmd with Matchers with ScalaFu
     }
   }
 
-  def getAuthKey(wp: WskProps): String = {
-    val authKey = wp.authKey.split(":")
-    s"${authKey(0)}:${authKey(1)}"
-  }
-
   def getParamsAnnos(parameters: Map[String, JsValue] = Map(),
                      annotations: Map[String, JsValue] = Map(),
                      parameterFile: Option[String] = None,
@@ -1345,9 +1369,12 @@ class RunWskRestCmd() extends FlatSpec with RunWskCmd with Matchers with ScalaFu
                    parameterFile: Option[String] = None,
                    blocking: Boolean = false,
                    result: Boolean = false,
+                   web: Boolean = false,
                    expectedExitCode: Int = Accepted.intValue)(implicit wp: WskProps): RestResult = {
     val (ns, actName) = this.getNamespaceEntityName(name)
-    val path = Path(s"$basePath/namespaces/$ns/actions/$actName")
+    val path =
+      if (web) Path(s"$basePath/web/$systemNamespace/$actName.http")
+      else Path(s"$basePath/namespaces/$ns/actions/$actName")
     var paramMap = Map("blocking" -> blocking.toString, "result" -> result.toString)
     val input = parameterFile map { pf =>
       Some(FileUtils.readFileToString(new File(pf)))
@@ -1496,11 +1523,11 @@ class RestResult(var statusCode: StatusCode, var respData: String = "", blocking
   }
 }
 
-case class ApiAction(name: String,
-                     namespace: String,
-                     backendMethod: String = "POST",
-                     backendUrl: String,
-                     authkey: String) {
+class ApiAction(var name: String,
+                var namespace: String,
+                var backendMethod: String = "POST",
+                var backendUrl: String,
+                var authkey: String) {
   def toJson(): JsObject = {
     return JsObject(
       "name" -> name.toJson,
diff --git a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala
new file mode 100644
index 0000000..4f0f131
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala
@@ -0,0 +1,28 @@
+/*
+ * 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 whisk.core.apigw.actions.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliRoutemgmtActionTests extends ApiGwRoutemgmtActionTests {
+  override lazy val wsk = new Wsk
+}
diff --git a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRestRoutemgmtActionTests.scala b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRestRoutemgmtActionTests.scala
new file mode 100644
index 0000000..aff8072
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRestRoutemgmtActionTests.scala
@@ -0,0 +1,31 @@
+/*
+ * 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 whisk.core.apigw.actions.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.rest.WskRest
+
+/**
+ * Tests for basic CLI usage. Some of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwRestRoutemgmtActionTests extends ApiGwRoutemgmtActionTests {
+  override lazy val wsk = new WskRest
+}
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 fa56de6..57449e6 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
@@ -24,38 +24,23 @@ import org.scalatest.junit.JUnitRunner
 import common.JsHelpers
 import common.StreamLogging
 import common.TestHelpers
-import common.TestUtils.ANY_ERROR_EXIT
 import common.TestUtils.DONTCARE_EXIT
 import common.TestUtils.RunResult
 import common.TestUtils.SUCCESS_EXIT
-import common.Wsk
+import common.BaseWsk
 import common.WskActorSystem
 import common.WskAdmin
 import common.WskProps
+import common.rest.ApiAction
 import common.WskTestHelpers
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 
-case class ApiAction(name: String,
-                     namespace: String,
-                     backendMethod: String = "POST",
-                     backendUrl: String,
-                     authkey: String) {
-  def toJson(): JsObject = {
-    return JsObject(
-      "name" -> name.toJson,
-      "namespace" -> namespace.toJson,
-      "backendMethod" -> backendMethod.toJson,
-      "backendUrl" -> backendUrl.toJson,
-      "authkey" -> authkey.toJson)
-  }
-}
-
 /**
  * Tests for basic CLI usage. Some of these tests require a deployed backend.
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwRoutemgmtActionTests
+abstract class ApiGwRoutemgmtActionTests
     extends TestHelpers
     with BeforeAndAfterAll
     with WskActorSystem
@@ -65,7 +50,7 @@ class ApiGwRoutemgmtActionTests
 
   val systemId = "whisk.system"
   implicit val wskprops = WskProps(authKey = WskAdmin.listKeys(systemId)(0)._1, namespace = systemId)
-  val wsk = new Wsk
+  val wsk: BaseWsk
 
   def getApis(bpOrName: Option[String],
               relpath: Option[String] = None,
@@ -299,7 +284,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       val createResult = createApi(
@@ -330,7 +315,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       val createResult = createApi(
@@ -366,7 +351,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       var createResult = createApi(
@@ -393,202 +378,4 @@ class ApiGwRoutemgmtActionTests
         deleteApi(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, "Invalid authentication.", Seq()),
-      //deleteApi
-      (
-        "/whisk.system/apimgmt/deleteApi",
-        ANY_ERROR_EXIT,
-        "Invalid authentication.",
-        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)
-    }
-  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
new file mode 100644
index 0000000..ba886c0
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
@@ -0,0 +1,33 @@
+/*
+ * 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 whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+import common.TestUtils.SUCCESS_EXIT
+
+/**
+ * Tests for basic CLI usage. Some of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliTests extends ApiGwTests {
+  override lazy val wsk: common.Wsk = new Wsk
+  override lazy val createCode = SUCCESS_EXIT
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwRestTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwRestTests.scala
new file mode 100644
index 0000000..8809953
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwRestTests.scala
@@ -0,0 +1,248 @@
+/*
+ * 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 whisk.core.cli.test
+
+import akka.http.scaladsl.model.StatusCodes.OK
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import spray.json.JsObject
+
+import common.rest.WskRest
+import common.rest.RestResult
+import common.TestUtils.RunResult
+
+/**
+ * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwRestTests extends ApiGwTests {
+  override lazy val wsk = new WskRest
+  override lazy val createCode = OK.intValue
+
+  override def verifyBadCommands(rr: RunResult, badpath: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val error = RestResult.getField(apiResultRest.respBody, "error")
+    error should include("Error: Resource path must begin with '/'.")
+  }
+
+  override def verifyBadCommandsDelete(rr: RunResult, badpath: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val error = RestResult.getField(apiResultRest.respBody, "error")
+    error should include(s"API deletion failure: API '/basepath' does not exist")
+  }
+
+  override def verifyBadCommandsList(rr: RunResult, badpath: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apis = apiResultRest.getFieldListJsObject("apis")
+    apis.size shouldBe 0
+  }
+
+  override def verifyInvalidCommands(rr: RunResult, badverb: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val error = apiResultRest.getField("error")
+    error should include(s"Error: Resource verb '${badverb}' not supported")
+  }
+
+  override def verifyInvalidCommandsDelete(rr: RunResult, badverb: String): Unit = {
+    verifyBadCommandsDelete(rr, badverb)
+  }
+
+  override def verifyInvalidCommandsList(rr: RunResult, badverb: String): Unit = {
+    verifyBadCommandsList(rr, badverb)
+  }
+
+  override def verifyNonJsonSwagger(rr: RunResult, filename: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val error = apiResultRest.getField("error")
+    error should include(s"swagger field cannot be parsed. Ensure it is valid JSON")
+  }
+
+  override def verifyMissingField(rr: RunResult): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val error = apiResultRest.getField("error")
+    error should include(s"swagger is missing the basePath field.")
+  }
+
+  override def verifyApiCreated(rr: RunResult): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    apiResultRest.statusCode shouldBe OK
+  }
+
+  def verifyList(rr: RunResult,
+                 namespace: String,
+                 actionName: String,
+                 testurlop: String,
+                 testbasepath: String,
+                 testrelpath: String,
+                 testapiname: String,
+                 newEndpoint: String = ""): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    val basepath = RestResult.getField(apidoc, "basePath")
+    basepath shouldBe testbasepath
+
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    paths.fields.contains(testrelpath) shouldBe true
+
+    val info = RestResult.getFieldJsObject(apidoc, "info")
+    val title = RestResult.getField(info, "title")
+    title shouldBe testapiname
+
+    verifyPaths(paths, testrelpath, testurlop, actionName, namespace)
+
+    if (newEndpoint != "") {
+      verifyPaths(paths, newEndpoint, testurlop, actionName, namespace)
+    }
+  }
+
+  def verifyPaths(paths: JsObject,
+                  testrelpath: String,
+                  testurlop: String,
+                  actionName: String,
+                  namespace: String = "") = {
+    val relpath = RestResult.getFieldJsObject(paths, testrelpath)
+    val urlop = RestResult.getFieldJsObject(relpath, testurlop)
+    val openwhisk = RestResult.getFieldJsObject(urlop, "x-openwhisk")
+    val actionN = RestResult.getField(openwhisk, "action")
+    actionN shouldBe actionName
+
+    if (namespace != "") {
+      val namespaceS = RestResult.getField(openwhisk, "namespace")
+      namespaceS shouldBe namespace
+    }
+  }
+
+  override def verifyApiList(rr: RunResult,
+                             clinamespace: String,
+                             actionName: String,
+                             testurlop: String,
+                             testbasepath: String,
+                             testrelpath: String,
+                             testapiname: String): Unit = {
+    verifyList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+  }
+
+  override def verifyApiGet(rr: RunResult): Unit = {
+    rr.stdout should include regex (s""""operationId":"getPathWithSub_pathsInIt"""")
+  }
+
+  override def verifyApiFullList(rr: RunResult,
+                                 clinamespace: String,
+                                 actionName: String,
+                                 testurlop: String,
+                                 testbasepath: String,
+                                 testrelpath: String,
+                                 testapiname: String): Unit = {
+    verifyList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+  }
+
+  override def verifyApiFullListDouble(rr: RunResult,
+                                       clinamespace: String,
+                                       actionName: String,
+                                       testurlop: String,
+                                       testbasepath: String,
+                                       testrelpath: String,
+                                       testapiname: String,
+                                       newEndpoint: String): Unit = {
+    verifyList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname, newEndpoint)
+  }
+
+  override def verifyApiDeleted(rr: RunResult): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    apiResultRest.statusCode shouldBe OK
+  }
+
+  override def verifyApiDeletedRelpath(rr: RunResult,
+                                       testrelpath: String,
+                                       testbasepath: String,
+                                       op: String = ""): Unit = {
+    verifyApiDeleted(rr)
+  }
+
+  override def verifyApiNameGet(rr: RunResult,
+                                testbasepath: String,
+                                actionName: String,
+                                responseType: String = "json"): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+
+    val config = RestResult.getFieldJsObject(apidoc, "x-ibm-configuration")
+
+    val cors = RestResult.getFieldJsObject(config, "cors")
+    val enabled = RestResult.getFieldJsValue(cors, "enabled").toString()
+    enabled shouldBe "true"
+
+    val basepath = RestResult.getField(apidoc, "basePath")
+    basepath shouldBe testbasepath
+
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    val relpath = RestResult.getFieldJsObject(paths, "/path")
+    val urlop = RestResult.getFieldJsObject(relpath, "get")
+    val openwhisk = RestResult.getFieldJsObject(urlop, "x-openwhisk")
+    val actionN = RestResult.getField(openwhisk, "action")
+    actionN shouldBe actionName
+    rr.stdout should include regex (s""""target-url":".*${actionName}.${responseType}"""")
+  }
+
+  override def verifyInvalidSwagger(rr: RunResult): Unit = {
+    verifyMissingField(rr)
+  }
+
+  override def verifyApiOp(rr: RunResult, testurlop: String, testapiname: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    val info = RestResult.getFieldJsObject(apidoc, "info")
+    val title = RestResult.getField(info, "title")
+    title shouldBe testapiname
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    val relpath = RestResult.getFieldJsObject(paths, "/")
+    val urlop = RestResult.getFieldJsObject(relpath, testurlop)
+    relpath.fields.contains(testurlop) shouldBe true
+  }
+
+  override def verifyApiBaseRelPath(rr: RunResult, testbasepath: String, testrelpath: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    val basepath = RestResult.getField(apidoc, "basePath")
+    basepath shouldBe testbasepath
+
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    paths.fields.contains(testrelpath) shouldBe true
+  }
+
+  override def verifyApiOpVerb(rr: RunResult, testurlop: String): Unit = {
+    val apiResultRest = rr.asInstanceOf[RestResult]
+    val apiValue = RestResult.getFieldJsObject(apiResultRest.getFieldListJsObject("apis")(0), "value")
+    val apidoc = RestResult.getFieldJsObject(apiValue, "apidoc")
+    val paths = RestResult.getFieldJsObject(apidoc, "paths")
+    val relpath = RestResult.getFieldJsObject(paths, "/")
+    val urlop = RestResult.getFieldJsObject(relpath, testurlop)
+    relpath.fields.contains(testurlop) shouldBe true
+  }
+
+  override def verifyInvalidKey(rr: RunResult): Unit = {
+    rr.stderr should include("A valid auth key is required")
+  }
+
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
index 9f152b0..145ea6b 100644
--- a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
@@ -20,149 +20,147 @@ package whisk.core.cli.test
 import java.io.File
 import java.io.BufferedWriter
 import java.io.FileWriter
-import java.time.Instant
-
-import scala.collection.mutable.ArrayBuffer
-import scala.concurrent.duration._
 
 import org.junit.runner.RunWith
 
-import org.scalatest.BeforeAndAfterAll
-import org.scalatest.BeforeAndAfterEach
 import org.scalatest.junit.JUnitRunner
 
-import common.TestHelpers
 import common.TestUtils._
 import common.TestUtils
-import common.WhiskProperties
-import common.Wsk
 import common.WskProps
-import common.WskTestHelpers
 
 /**
  * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach with BeforeAndAfterAll {
+abstract class ApiGwTests extends BaseApiGwTests {
 
-  implicit val wskprops = WskProps()
-  val wsk = new Wsk
   val clinamespace = wsk.namespace.whois()
+  val createCode: Int
 
-  // This test suite makes enough CLI invocations in 60 seconds to trigger the OpenWhisk
-  // throttling restriction.  To avoid CLI failures due to being throttled, track the
-  // CLI invocation calls and when at the throttle limit, pause the next CLI invocation
-  // with exactly enough time to relax the throttling.
-  val maxActionsPerMin = WhiskProperties.getMaxActionInvokesPerMinute()
-  val invocationTimes = new ArrayBuffer[Instant]()
-
-  // Custom CLI properties file
-  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
-
-  /**
-   * Expected to be called before each test.
-   * Assumes that each test will not invoke more than 5 actions and
-   * settle the throttle when there isn't enough capacity to handle the test.
-   */
-  def checkThrottle(maxInvocationsBeforeThrottle: Int = maxActionsPerMin, expectedActivationsPerTest: Int = 5) = {
-    val t = Instant.now
-    val tminus60 = t.minusSeconds(60)
-    val invocationsLast60Seconds = invocationTimes.filter(_.isAfter(tminus60)).sorted
-    val invocationCount = invocationsLast60Seconds.length
-    println(s"Action invokes within last minute: ${invocationCount}")
-
-    if (invocationCount >= maxInvocationsBeforeThrottle) {
-      // Instead of waiting a fixed 60 seconds to settle the throttle,
-      // calculate a wait time that will clear out about half of the
-      // current invocations (assuming even distribution) from the
-      // next 60 second period.
-      val oldestInvocationInLast60Seconds = invocationsLast60Seconds.head
-
-      // Take the oldest invocation time in this 60 second period.  To clear
-      // this invocation from the next 60 second period, the wait time will be
-      // (60sec - oldest invocation's delta time away from the period end).
-      // This will clear all of the invocations from the next period at the
-      // expense of potentially waiting uncessarily long. Instead, this calculation
-      // halves the delta time as a compromise.
-      val throttleTime = 60.seconds.toMillis - ((t.toEpochMilli - oldestInvocationInLast60Seconds.toEpochMilli) / 2)
-      println(s"Waiting ${throttleTime} milliseconds to settle the throttle")
-      Thread.sleep(throttleTime)
-    }
+  def verifyBadCommands(rr: RunResult, badpath: String): Unit = {
+    rr.stderr should include(s"'${badpath}' must begin with '/'")
+  }
+
+  def verifyBadCommandsDelete(rr: RunResult, badpath: String): Unit = {
+    verifyBadCommands(rr, badpath)
+  }
+
+  def verifyBadCommandsList(rr: RunResult, badpath: String): Unit = {
+    verifyBadCommands(rr, badpath)
+  }
+
+  def verifyInvalidCommands(rr: RunResult, badverb: String): Unit = {
+    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+  }
+
+  def verifyInvalidCommandsDelete(rr: RunResult, badverb: String): Unit = {
+    verifyInvalidCommands(rr, badverb)
+  }
+
+  def verifyInvalidCommandsList(rr: RunResult, badverb: String): Unit = {
+    verifyInvalidCommands(rr, badverb)
+  }
+
+  def verifyNonJsonSwagger(rr: RunResult, filename: String): Unit = {
+    rr.stderr should include(s"Error parsing swagger file '${filename}':")
+  }
+
+  def verifyMissingField(rr: RunResult): Unit = {
+    rr.stderr should include(s"Swagger file is invalid (missing basePath, info, paths, or swagger fields")
+  }
+
+  def verifyApiCreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+  }
 
-    invocationTimes += Instant.now
+  def verifyApiList(rr: RunResult,
+                    clinamespace: String,
+                    actionName: String,
+                    testurlop: String,
+                    testbasepath: String,
+                    testrelpath: String,
+                    testapiname: String): Unit = {
+    rr.stdout should include("ok: APIs")
+    rr.stdout should include regex (s"Action:\\s+/${clinamespace}/${actionName}\n")
+    rr.stdout should include regex (s"Verb:\\s+${testurlop}\n")
+    rr.stdout should include regex (s"Base path:\\s+${testbasepath}\n")
+    rr.stdout should include regex (s"Path:\\s+${testrelpath}\n")
+    rr.stdout should include regex (s"API Name:\\s+${testapiname}\n")
+    rr.stdout should include regex (s"URL:\\s+")
+    rr.stdout should include(testbasepath + testrelpath)
   }
 
-  override def beforeEach() = {
-    //checkThrottle()
+  def verifyApiBaseRelPath(rr: RunResult, testbasepath: String, testrelpath: String): Unit = {
+    rr.stdout should include(testbasepath + testrelpath)
   }
 
-  /*
-   * Create a CLI properties file for use by the tests
-   */
-  override def beforeAll() = {
-    cliWskPropsFile.deleteOnExit()
-    val wskprops = WskProps(token = "SOME TOKEN")
-    wskprops.writeFile(cliWskPropsFile)
-    println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
+  def verifyApiGet(rr: RunResult): Unit = {
+    rr.stdout should include regex (s""""operationId":\\s+"getPathWithSub_pathsInIt"""")
   }
 
-  /*
-   * Forcibly clear the throttle so that downstream tests are not affected by
-   * this test suite
-   */
-  override def afterAll() = {
-    // Check and settle the throttle so that this test won't cause issues with and follow on tests
-    checkThrottle(30)
+  def verifyApiFullList(rr: RunResult,
+                        clinamespace: String,
+                        actionName: String,
+                        testurlop: String,
+                        testbasepath: String,
+                        testrelpath: String,
+                        testapiname: String): Unit = {
+
+    rr.stdout should include("ok: APIs")
+    if (clinamespace == "") {
+      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+    } else {
+      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+    }
+    rr.stdout should include(testbasepath + testrelpath)
+
+  }
+
+  def verifyApiFullListDouble(rr: RunResult,
+                              clinamespace: String,
+                              actionName: String,
+                              testurlop: String,
+                              testbasepath: String,
+                              testrelpath: String,
+                              testapiname: String,
+                              newEndpoint: String): Unit = {
+    verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+    rr.stdout should include(testbasepath + newEndpoint)
+  }
+
+  def verifyApiDeleted(rr: RunResult): Unit = {
+    rr.stdout should include("ok: deleted API")
   }
 
-  def apiCreate(basepath: Option[String] = None,
-                relpath: Option[String] = None,
-                operation: Option[String] = None,
-                action: Option[String] = None,
-                apiname: Option[String] = None,
-                swagger: Option[String] = None,
-                responsetype: Option[String] = None,
-                expectedExitCode: Int = SUCCESS_EXIT,
-                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()))(
-    implicit wskpropsOverride: WskProps): RunResult = {
-
-    checkThrottle()
-    wsk.api.create(basepath, relpath, operation, action, apiname, swagger, responsetype, expectedExitCode, cliCfgFile)(
-      wskpropsOverride)
+  def verifyApiDeletedRelpath(rr: RunResult, testrelpath: String, testbasepath: String, op: String = ""): Unit = {
+    if (op != "")
+      rr.stdout should include("ok: deleted " + testrelpath + " " + op.toUpperCase() + " from " + testbasepath)
+    else
+      rr.stdout should include("ok: deleted " + testrelpath + " from " + testbasepath)
   }
 
-  def apiList(basepathOrApiName: Option[String] = None,
-              relpath: Option[String] = None,
-              operation: Option[String] = None,
-              limit: Option[Int] = None,
-              since: Option[Instant] = None,
-              full: Option[Boolean] = None,
-              nameSort: Option[Boolean] = None,
-              expectedExitCode: Int = SUCCESS_EXIT,
-              cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
-
-    checkThrottle()
-    wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, nameSort, expectedExitCode, cliCfgFile)
+  def verifyApiNameGet(rr: RunResult, testbasepath: String, actionName: String, responseType: String = "json"): Unit = {
+    rr.stdout should include(testbasepath)
+    rr.stdout should include(s"${actionName}")
+    rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+    rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
   }
 
-  def apiGet(basepathOrApiName: Option[String] = None,
-             full: Option[Boolean] = None,
-             expectedExitCode: Int = SUCCESS_EXIT,
-             cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()),
-             format: Option[String] = None): RunResult = {
+  def verifyInvalidSwagger(rr: RunResult): Unit = {
+    rr.stderr should include(s"Swagger file is invalid")
+  }
 
-    checkThrottle()
-    wsk.api.get(basepathOrApiName, full, expectedExitCode, cliCfgFile, format)
+  def verifyApiOp(rr: RunResult, testurlop: String, testapiname: String): Unit = {
+    rr.stdout should include regex (s"\\s+${testurlop}\\s+${testapiname}\\s+")
   }
 
-  def apiDelete(basepathOrApiName: String,
-                relpath: Option[String] = None,
-                operation: Option[String] = None,
-                expectedExitCode: Int = SUCCESS_EXIT,
-                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+  def verifyApiOpVerb(rr: RunResult, testurlop: String): Unit = {
+    rr.stdout should include regex (s"Verb:\\s+${testurlop}")
+  }
 
-    checkThrottle()
-    wsk.api.delete(basepathOrApiName, relpath, operation, expectedExitCode, cliCfgFile)
+  def verifyInvalidKey(rr: RunResult): Unit = {
+    rr.stderr should include("The supplied authentication is invalid")
   }
 
   behavior of "Wsk api"
@@ -176,21 +174,21 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       operation = Some("GET"),
       action = Some("action"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommands(rr, badpath)
 
     rr = apiDelete(
       basepathOrApiName = "/basepath",
       relpath = Some(badpath),
       operation = Some("GET"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommandsDelete(rr, badpath)
 
     rr = apiList(
       basepathOrApiName = Some("/basepath"),
       relpath = Some(badpath),
       operation = Some("GET"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommandsList(rr, badpath)
   }
 
   it should "reject an api commands with an invalid verb parameter" in {
@@ -202,28 +200,28 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       operation = Some(badverb),
       action = Some("action"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommands(rr, badverb)
 
     rr = apiDelete(
       basepathOrApiName = "/basepath",
       relpath = Some("/path"),
       operation = Some(badverb),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommandsDelete(rr, badverb)
 
     rr = apiList(
       basepathOrApiName = Some("/basepath"),
       relpath = Some("/path"),
       operation = Some(badverb),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommandsList(rr, badverb)
   }
 
   it should "reject an api create command that specifies a nonexistent configuration file" in {
     val configfile = "/nonexistent/file"
 
     val rr = apiCreate(swagger = Some(configfile), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Error reading swagger file '${configfile}':")
+    rr.stderr should include(s"Error reading swagger file '${configfile}'")
   }
 
   it should "reject an api create command specifying a non-JSON configuration file" in {
@@ -236,7 +234,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     bw.close()
 
     val rr = apiCreate(swagger = Some(filename), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Error parsing swagger file '${filename}':")
+    verifyNonJsonSwagger(rr, filename)
   }
 
   it should "reject an api create command specifying a non-swagger JSON configuration file" in {
@@ -261,7 +259,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     bw.close()
 
     val rr = apiCreate(swagger = Some(filename), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Swagger file is invalid (missing basePath, info, paths, or swagger fields")
+    verifyMissingField(rr)
   }
 
   it should "verify full list output" in {
@@ -276,7 +274,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       println("cli namespace: " + clinamespace)
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -285,21 +283,14 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         action = Some(actionName),
         apiname = Some(testapiname))
       println("api create: " + rr.stdout)
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(
         basepathOrApiName = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         full = Some(true))
       println("api list: " + rr.stdout)
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"Action:\\s+/${clinamespace}/${actionName}\n")
-      rr.stdout should include regex (s"Verb:\\s+${testurlop}\n")
-      rr.stdout should include regex (s"Base path:\\s+${testbasepath}\n")
-      rr.stdout should include regex (s"Path:\\s+${testrelpath}\n")
-      rr.stdout should include regex (s"API Name:\\s+${testapiname}\n")
-      rr.stdout should include regex (s"URL:\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath)
@@ -319,7 +310,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -327,15 +318,13 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiGet(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include regex (s""""operationId":\\s+"getPathWithSub_pathsInIt"""")
+      verifyApiGet(rr)
       val deleteresult = apiDelete(basepathOrApiName = testbasepath)
-      deleteresult.stdout should include("ok: deleted API")
+      verifyApiDeleted(deleteresult)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -353,7 +342,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -361,12 +350,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiGet(basepathOrApiName = Some(testapiname))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.json""")
+      verifyApiNameGet(rr, testbasepath, actionName)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -384,7 +370,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -392,9 +378,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiDelete(basepathOrApiName = testapiname)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -412,7 +398,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -420,9 +406,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiDelete(basepathOrApiName = testbasepath)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -441,7 +427,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -449,19 +435,24 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath,
+        newEndpoint,
+        testapiname,
+        newEndpoint)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -479,14 +470,12 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     val swaggerPath = TestUtils.getTestApiGwFilename("testswaggerdoc1")
     try {
       var rr = apiCreate(swagger = Some(swaggerPath))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
       println("list stdout: " + rr.stdout)
       println("list stderr: " + rr.stderr)
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, "", actionName, testurlop, testbasepath, testrelpath, testapiname)
+
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -506,7 +495,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -514,14 +503,14 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname2))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
 
       // Update both APIs - each with a new endpoint
       rr = apiCreate(
@@ -529,25 +518,36 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
 
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath,
+        testrelpath,
+        testapiname,
+        newEndpoint)
 
       rr = apiList(basepathOrApiName = Some(testbasepath2))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath2 + testrelpath)
-      rr.stdout should include(testbasepath2 + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath2,
+        testrelpath,
+        testapiname2,
+        newEndpoint)
+
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -570,7 +570,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -578,13 +578,11 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       val deleteresult = apiDelete(basepathOrApiName = testbasepath)
-      deleteresult.stdout should include("ok: deleted API")
+      verifyApiDeleted(deleteresult)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -604,7 +602,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
       println("api create stdout: " + rr.stdout)
       println("api create stderr: " + rr.stderr)
-      rr.stderr should include(s"Swagger file is invalid")
+      verifyInvalidSwagger(rr)
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -621,7 +619,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -629,20 +627,18 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       var rr2 = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testnewrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr2.stdout should include("ok: created API")
+      verifyApiCreated(rr2)
       rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath))
-      rr.stdout should include("ok: deleted " + testrelpath + " from " + testbasepath)
+      verifyApiDeletedRelpath(rr, testrelpath, testbasepath)
       rr2 = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testnewrelpath))
-      rr2.stdout should include("ok: APIs")
-      rr2.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr2.stdout should include(testbasepath + testnewrelpath)
+      verifyApiFullList(rr2, clinamespace, actionName, testurlop, testbasepath, testnewrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -661,7 +657,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -669,22 +665,22 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop2),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop2, testbasepath, testrelpath, testapiname)
       rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath), operation = Some(testurlop2))
-      rr.stdout should include("ok: deleted " + testrelpath + " " + "POST" + " from " + testbasepath)
+      verifyApiDeletedRelpath(rr, testrelpath, testbasepath, testurlop2)
+
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -697,6 +693,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     val testrelpath = "/whisk_system/utils/echo"
     val testrelpath2 = "/whisk_system/utils/split"
     val testurlop = "get"
+    val testurlop2 = "post"
     val testapiname = testName + " API Name"
     val actionName = "test1a"
     val swaggerPath = TestUtils.getTestApiGwFilename(s"testswaggerdoc2")
@@ -704,13 +701,10 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       var rr = apiCreate(swagger = Some(swaggerPath))
       println("api create stdout: " + rr.stdout)
       println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + testrelpath2)
+      verifyApiFullList(rr, "", actionName, testurlop, testbasepath, testrelpath, testapiname)
+      verifyApiFullList(rr, "", actionName, testurlop2, testbasepath, testrelpath2, testapiname)
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -729,7 +723,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -737,30 +731,24 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname2))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath2 + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath2, testrelpath, testapiname2)
       rr = apiDelete(basepathOrApiName = testbasepath2)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiDelete(basepathOrApiName = testbasepath)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -768,55 +756,6 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     }
   }
 
-  it should "reject an API created with a non-existent action" in {
-    val testName = "CLI_APIGWTEST15"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    try {
-      val rr = apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)
-      rr.stderr should include("does not exist")
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject an API created with an action that is not a web action" in {
-    val testName = "CLI_APIGWTEST16"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    try {
-      // Create the action for the API.  It must NOT be a "web-action" action for this test
-      val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT)
-
-      val rr = apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)
-      rr.stderr should include("is not a web action")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
   it should "verify API with http response type " in {
     val testName = "CLI_APIGWTEST17"
     val testbasepath = "/" + testName + "_bp"
@@ -829,133 +768,25 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-
-      apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
-
-      val rr = apiGet(basepathOrApiName = Some(testapiname))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject API export when export type is invalid" in {
-    val testName = "CLI_APIGWTEST18"
-    val testbasepath = "/" + testName + "_bp"
-
-    val rr = apiGet(basepathOrApiName = Some(testbasepath), format = Some("BadType"), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include("Invalid format type")
-  }
-
-  it should "successfully export an API in YAML format" in {
-    val testName = "CLI_APIGWTEST19"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    val responseType = "http"
-    try {
-      // Create the action for the API.  It must be a "web-action" action.
-      val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
-      apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
-
-      val rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("yaml"))
-      rr.stdout should include(s"basePath: ${testbasepath}")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "successfully export an API when JSON format is explcitly specified" in {
-    val testName = "CLI_APIGWTEST20"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    val responseType = "http"
-    try {
-      // Create the action for the API.  It must be a "web-action" action.
-      val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-
-      apiCreate(
+      var rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
+        responsetype = Some(responseType))
+      verifyApiCreated(rr)
 
-      val rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("json"))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
+      rr = apiGet(basepathOrApiName = Some(testapiname))
+      verifyApiNameGet(rr, testbasepath, actionName, responseType)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
   }
 
-  it should "successfully create an API from a YAML formatted API configuration file" in {
-    val testName = "CLI_APIGWTEST21"
-    val testbasepath = "/bp"
-    val testrelpath = "/rp"
-    val testurlop = "get"
-    val testapiname = testbasepath
-    val actionName = "webhttpecho"
-    val swaggerPath = TestUtils.getTestApiGwFilename(s"local.api.yaml")
-    try {
-      var rr = apiCreate(swagger = Some(swaggerPath))
-      println("api create stdout: " + rr.stdout)
-      println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
-      rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject creation of an API from invalid YAML formatted API configuration file" in {
-    val testName = "CLI_APIGWTEST22"
-    val testbasepath = "/" + testName + "_bp"
-    val swaggerPath = TestUtils.getTestApiGwFilename(s"local.api.bad.yaml")
-    try {
-      val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
-      println("api create stdout: " + rr.stdout)
-      println("api create stderror: " + rr.stderr)
-      rr.stderr should include("Unable to parse YAML configuration file")
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
   it should "reject deletion of a non-existent api" in {
     val nonexistentApi = "/not-there"
 
@@ -975,21 +806,21 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       var rr = apiCreate(swagger = Some(swaggerPath))
       println("api create stdout: " + rr.stdout)
       println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
+      this.verifyApiCreated(rr)
 
       rr = apiList(basepathOrApiName = Some(testbasepath))
       println("api list:\n" + rr.stdout)
       testops foreach { testurlop =>
-        rr.stdout should include regex (s"\\s+${testurlop}\\s+${testapiname}\\s+")
+        verifyApiOp(rr, testurlop, testapiname)
       }
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiBaseRelPath(rr, testbasepath, testrelpath)
 
       rr = apiList(basepathOrApiName = Some(testbasepath), full = Some(true))
       println("api full list:\n" + rr.stdout)
       testops foreach { testurlop =>
-        rr.stdout should include regex (s"Verb:\\s+${testurlop}")
+        verifyApiOpVerb(rr, testurlop)
       }
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiBaseRelPath(rr, testbasepath, testrelpath)
 
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -1007,58 +838,22 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.
       val file = TestUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       // Set an invalid auth key
       val badWskProps = WskProps(authKey = "bad-auth-key")
 
-      apiCreate(
+      val rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)(badWskProps).stderr should include("The supplied authentication is invalid")
+        expectedExitCode = ANY_ERROR_EXIT)(badWskProps)
+      verifyInvalidKey(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
   }
-
-  it should "list api alphabetically by Base/Rel/Verb" in {
-    val baseName = "/BaseTestPathApiList"
-    val actionName = "actionName"
-    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
-    try {
-      // Create Action for apis
-      var action =
-        wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-      println("action creation: " + action.stdout)
-      // Create apis
-      for (i <- 1 to 3) {
-        val base = s"$baseName$i"
-        var api = apiCreate(
-          basepath = Some(base),
-          relpath = Some("/relPath"),
-          operation = Some("GET"),
-          action = Some(actionName))
-        println("api creation: " + api.stdout)
-      }
-      val original = apiList(nameSort = Some(true)).stdout
-      val originalFull = apiList(full = Some(true), nameSort = Some(true)).stdout
-      val scalaSorted = List(s"${baseName}1" + "/", s"${baseName}2" + "/", s"${baseName}3" + "/")
-      val regex = s"${baseName}[1-3]/".r
-      val list = (regex.findAllMatchIn(original)).toList
-      val listFull = (regex.findAllMatchIn(originalFull)).toList
-
-      scalaSorted.toString shouldEqual list.toString
-      scalaSorted.toString shouldEqual listFull.toString
-    } finally {
-      // Clean up Apis
-      for (i <- 1 to 3) {
-        apiDelete(basepathOrApiName = s"${baseName}$i", expectedExitCode = DONTCARE_EXIT)
-      }
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala
new file mode 100644
index 0000000..f55ab79
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala
@@ -0,0 +1,165 @@
+/*
+ * 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 whisk.core.cli.test
+
+import java.io.File
+import java.time.Instant
+
+import scala.collection.mutable.ArrayBuffer
+import scala.concurrent.duration._
+
+import org.junit.runner.RunWith
+
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.BeforeAndAfterEach
+import org.scalatest.junit.JUnitRunner
+
+import common.TestHelpers
+import common.TestUtils._
+import common.WhiskProperties
+import common.BaseWsk
+import common.WskProps
+import common.WskTestHelpers
+
+/**
+ * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+abstract class BaseApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach with BeforeAndAfterAll {
+
+  implicit val wskprops = WskProps()
+  val wsk: BaseWsk
+
+  // This test suite makes enough CLI invocations in 60 seconds to trigger the OpenWhisk
+  // throttling restriction.  To avoid CLI failures due to being throttled, track the
+  // CLI invocation calls and when at the throttle limit, pause the next CLI invocation
+  // with exactly enough time to relax the throttling.
+  val maxActionsPerMin = WhiskProperties.getMaxActionInvokesPerMinute()
+  val invocationTimes = new ArrayBuffer[Instant]()
+
+  // Custom CLI properties file
+  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+
+  /**
+   * Expected to be called before each test.
+   * Assumes that each test will not invoke more than 5 actions and
+   * settle the throttle when there isn't enough capacity to handle the test.
+   */
+  def checkThrottle(maxInvocationsBeforeThrottle: Int = maxActionsPerMin, expectedActivationsPerTest: Int = 5) = {
+    val t = Instant.now
+    val tminus60 = t.minusSeconds(60)
+    val invocationsLast60Seconds = invocationTimes.filter(_.isAfter(tminus60)).sorted
+    val invocationCount = invocationsLast60Seconds.length
+    println(s"Action invokes within last minute: ${invocationCount}")
+
+    if (invocationCount >= maxInvocationsBeforeThrottle) {
+      // Instead of waiting a fixed 60 seconds to settle the throttle,
+      // calculate a wait time that will clear out about half of the
+      // current invocations (assuming even distribution) from the
+      // next 60 second period.
+      val oldestInvocationInLast60Seconds = invocationsLast60Seconds.head
+
+      // Take the oldest invocation time in this 60 second period.  To clear
+      // this invocation from the next 60 second period, the wait time will be
+      // (60sec - oldest invocation's delta time away from the period end).
+      // This will clear all of the invocations from the next period at the
+      // expense of potentially waiting uncessarily long. Instead, this calculation
+      // halves the delta time as a compromise.
+      val throttleTime = 60.seconds.toMillis - ((t.toEpochMilli - oldestInvocationInLast60Seconds.toEpochMilli) / 2)
+      println(s"Waiting ${throttleTime} milliseconds to settle the throttle")
+      Thread.sleep(throttleTime)
+    }
+
+    invocationTimes += Instant.now
+  }
+
+  override def beforeEach() = {
+    //checkThrottle()
+  }
+
+  /*
+   * Create a CLI properties file for use by the tests
+   */
+  override def beforeAll() = {
+    cliWskPropsFile.deleteOnExit()
+    val wskprops = WskProps(token = "SOME TOKEN")
+    wskprops.writeFile(cliWskPropsFile)
+    println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
+  }
+
+  /*
+   * Forcibly clear the throttle so that downstream tests are not affected by
+   * this test suite
+   */
+  override def afterAll() = {
+    // Check and settle the throttle so that this test won't cause issues with and follow on tests
+    checkThrottle(30)
+  }
+
+  def apiCreate(basepath: Option[String] = None,
+                relpath: Option[String] = None,
+                operation: Option[String] = None,
+                action: Option[String] = None,
+                apiname: Option[String] = None,
+                swagger: Option[String] = None,
+                responsetype: Option[String] = None,
+                expectedExitCode: Int = SUCCESS_EXIT,
+                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()))(
+    implicit wskpropsOverride: WskProps): RunResult = {
+
+    println("parmssss is fdasdadasdddddddddsfsd")
+    checkThrottle()
+    println("create is fdasdadasdddddddddsfsd")
+    wsk.api.create(basepath, relpath, operation, action, apiname, swagger, responsetype, expectedExitCode, cliCfgFile)(
+      wskpropsOverride)
+  }
+
+  def apiList(basepathOrApiName: Option[String] = None,
+              relpath: Option[String] = None,
+              operation: Option[String] = None,
+              limit: Option[Int] = None,
+              since: Option[Instant] = None,
+              full: Option[Boolean] = None,
+              nameSort: Option[Boolean] = None,
+              expectedExitCode: Int = SUCCESS_EXIT,
+              cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+
+    checkThrottle()
+    wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, nameSort, expectedExitCode, cliCfgFile)
+  }
+
+  def apiGet(basepathOrApiName: Option[String] = None,
+             full: Option[Boolean] = None,
+             expectedExitCode: Int = SUCCESS_EXIT,
+             cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()),
+             format: Option[String] = None): RunResult = {
+
+    checkThrottle()
+    wsk.api.get(basepathOrApiName, full, expectedExitCode, cliCfgFile, format)
+  }
+
+  def apiDelete(basepathOrApiName: String,
+                relpath: Option[String] = None,
+                operation: Option[String] = None,
+                expectedExitCode: Int = SUCCESS_EXIT,
+                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+
+    checkThrottle()
+    wsk.api.delete(basepathOrApiName, relpath, operation, expectedExitCode, cliCfgFile)
+  }
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskCliApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskCliApiGwTests.scala
new file mode 100644
index 0000000..088b6c5
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/WskCliApiGwTests.scala
@@ -0,0 +1,438 @@
+/*
+ * 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 whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.JsHelpers
+import common.StreamLogging
+import common.TestUtils
+import common.TestUtils.ANY_ERROR_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.SUCCESS_EXIT
+import common.Wsk
+import common.WskActorSystem
+import common.WskAdmin
+import common.WskProps
+
+/**
+ * Tests for basic CLI usage. Some of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class WskCliApiGwTests extends BaseApiGwTests with WskActorSystem with JsHelpers with StreamLogging {
+
+  val systemId: String = "whisk.system"
+  override implicit val wskprops = WskProps(authKey = WskAdmin.listKeys(systemId)(0)._1, namespace = systemId)
+  val wsk: common.Wsk = new Wsk
+
+  it should "reject apimgmt actions that are invoked with not enough parameters" in {
+    val invalidArgs = Seq(
+      //getApi
+      ("/whisk.system/apimgmt/getApi", ANY_ERROR_EXIT, "Invalid authentication.", Seq()),
+      //deleteApi
+      (
+        "/whisk.system/apimgmt/deleteApi",
+        ANY_ERROR_EXIT,
+        "Invalid authentication.",
+        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)
+    }
+  }
+
+  it should "reject an API created with a non-existent action" in {
+    val testName = "CLI_APIGWTEST15"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    try {
+      val rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        expectedExitCode = ANY_ERROR_EXIT)
+      rr.stderr should include("does not exist")
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject an API created with an action that is not a web action" in {
+    val testName = "CLI_APIGWTEST16"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    try {
+      // Create the action for the API.  It must NOT be a "web-action" action for this test
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT)
+
+      val rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        expectedExitCode = ANY_ERROR_EXIT)
+      rr.stderr should include("is not a web action")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject API export when export type is invalid" in {
+    val testName = "CLI_APIGWTEST18"
+    val testbasepath = "/" + testName + "_bp"
+
+    val rr = apiGet(basepathOrApiName = Some(testbasepath), format = Some("BadType"), expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include("Invalid format type")
+  }
+
+  it should "list api alphabetically by Base/Rel/Verb" in {
+    val baseName = "/BaseTestPathApiList"
+    val actionName = "actionName"
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    try {
+      // Create Action for apis
+      var action =
+        wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      println("action creation: " + action.stdout)
+      // Create apis
+      for (i <- 1 to 3) {
+        val base = s"$baseName$i"
+        var api = apiCreate(
+          basepath = Some(base),
+          relpath = Some("/relPath"),
+          operation = Some("GET"),
+          action = Some(actionName))
+        println("api creation: " + api.stdout)
+      }
+      val original = apiList(nameSort = Some(true))
+      val originalFull = apiList(full = Some(true), nameSort = Some(true))
+      val scalaSorted = List(s"${baseName}1" + "/", s"${baseName}2" + "/", s"${baseName}3" + "/")
+
+      val regex = s"${baseName}[1-3]/".r
+      val list = (regex.findAllMatchIn(original.stdout)).toList
+      val listFull = (regex.findAllMatchIn(originalFull.stdout)).toList
+
+      scalaSorted.toString shouldEqual list.toString
+      scalaSorted.toString shouldEqual listFull.toString
+
+    } finally {
+      // Clean up Apis
+      for (i <- 1 to 3) {
+        apiDelete(basepathOrApiName = s"${baseName}$i", expectedExitCode = DONTCARE_EXIT)
+      }
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully export an API in YAML format" in {
+    val testName = "CLI_APIGWTEST19"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    val responseType = "http"
+    try {
+      // Create the action for the API.  It must be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+
+      var rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        responsetype = Some(responseType))
+      rr.stdout should include("ok: created API")
+
+      rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("yaml"))
+      rr.stdout should include(s"basePath: ${testbasepath}")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully export an API when JSON format is explcitly specified" in {
+    val testName = "CLI_APIGWTEST20"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    val responseType = "http"
+    try {
+      // Create the action for the API.  It must be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+
+      var rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        responsetype = Some(responseType))
+      rr.stdout should include("ok: created API")
+
+      rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("json"))
+      rr.stdout should include(testbasepath)
+      rr.stdout should include(s"${actionName}")
+      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully create an API from a YAML formatted API configuration file" in {
+    val testName = "CLI_APIGWTEST21"
+    val testbasepath = "/bp"
+    val testrelpath = "/rp"
+    val testurlop = "get"
+    val testapiname = testbasepath
+    val actionName = "webhttpecho"
+    val swaggerPath = TestUtils.getTestApiGwFilename(s"local.api.yaml")
+    try {
+      var rr = apiCreate(swagger = Some(swaggerPath))
+      println("api create stdout: " + rr.stdout)
+      println("api create stderror: " + rr.stderr)
+      rr.stdout should include("ok: created API")
+      rr = apiList(basepathOrApiName = Some(testbasepath))
+      rr.stdout should include("ok: APIs")
+      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+      rr.stdout should include(testbasepath + testrelpath)
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject creation of an API from invalid YAML formatted API configuration file" in {
+    val testName = "CLI_APIGWTEST22"
+    val testbasepath = "/" + testName + "_bp"
+    val swaggerPath = TestUtils.getTestApiGwFilename(s"local.api.bad.yaml")
+    try {
+      val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
+      println("api create stdout: " + rr.stdout)
+      println("api create stderror: " + rr.stderr)
+      rr.stderr should include("Unable to parse YAML configuration file")
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+}

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

Mime
View raw message