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

[incubator-openwhisk-cli] 17/36: API GW V2 - Add 'wsk api` command (#2068)

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

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

commit 9ca38f88b555d34a5c598069f1deebecb1723f55
Author: Mark Deuser <md...@us.ibm.com>
AuthorDate: Thu Apr 13 17:39:25 2017 -0400

    API GW V2 - Add 'wsk api` command (#2068)
    
    * API GW V2 CLI - Add 'wsk api' command
    - Support both 'wsk api-experimental' and 'wsk api' commands (separate PR wil deprecate `api-experimental`)
    - 'wsk api' command requires that the target action exist and that it's a web action
    
    * API GW V2 CLI - 'wsk api' tests
    * Add "COMING SOON" label to 'wsk api' help
    * API update needs additional param
    * Reduce travis test execution time
---
 .../apigw/healthtests/ApiGwEndToEndTests.scala     | 137 ++++-
 .../scala/whisk/core/cli/test/ApiGwTests.scala     | 681 ++++++++++++++++++---
 2 files changed, 735 insertions(+), 83 deletions(-)

diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
index 1833d43..aa918ea 100644
--- a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
@@ -23,6 +23,7 @@ import java.io.FileWriter
 import scala.concurrent.duration.DurationInt
 
 import org.junit.runner.RunWith
+import org.scalatest.BeforeAndAfterAll
 import org.scalatest.FlatSpec
 import org.scalatest.Matchers
 import org.scalatest.junit.JUnitRunner
@@ -35,6 +36,7 @@ import common.TestUtils._
 import common.Wsk
 import common.WskAdmin
 import common.WskProps
+import common.WskPropsV2
 import common.WskTestHelpers
 import spray.json._
 import spray.json.DefaultJsonProtocol._
@@ -44,19 +46,40 @@ import system.rest.RestUtil
  * Basic tests of the download link for Go CLI binaries
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwEndToEndTests extends FlatSpec with Matchers with RestUtil with TestHelpers with WskTestHelpers {
+class ApiGwEndToEndTests
+    extends FlatSpec
+    with Matchers
+    with RestUtil
+    with TestHelpers
+    with WskTestHelpers
+    with BeforeAndAfterAll {
 
     implicit val wskprops = WskProps()
     val wsk = new Wsk
     val (cliuser, clinamespace) = WskAdmin.getUser(wskprops.authKey)
 
+    // Custom CLI properties file
+    val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+
+    /*
+     * Create a CLI properties file for use by the tests
+     */
+    override def beforeAll() = {
+        cliWskPropsFile.deleteOnExit()
+        val wskprops = WskPropsV2(token = "SOME TOKEN")
+        wskprops.writeFile(cliWskPropsFile)
+        println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
+    }
+
+    behavior of "Wsk api-experimental"
+
     it should s"create an API and successfully invoke that API" in {
-        val testName = "APIGW_HEALTHTEST1"
+        val testName = "APIGWe_HEALTHTEST1"
         val testbasepath = "/" + testName + "_bp"
         val testrelpath = "/path"
         val testurlop = "get"
         val testapiname = testName + " API Name"
-        val actionName = "echo"
+        val actionName = testName + "_echo"
         val urlqueryparam = "name"
         val urlqueryvalue = "test"
 
@@ -68,7 +91,84 @@ class ApiGwEndToEndTests extends FlatSpec with Matchers with RestUtil with TestH
             wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT)
 
             // Create the API
-            var rr = wsk.api.create(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+            var rr = wsk.apiexperimental.create(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+            rr.stdout should include("ok: created API")
+            val apiurl = rr.stdout.split("\n")(1)
+            println(s"apiurl: '${apiurl}'")
+
+            // Validate the API was successfully created
+            // List result will look like:
+            // ok: APIs
+            // Action                            Verb             API Name  URL
+            // /_//whisk.system/utils/echo          get  APIGW_HEALTHTEST1 API Name  http://172.17.0.1:9001/api/ab9082cd-ea8e-465a-8a65-b491725cc4ef/APIGW_HEALTHTEST1_bp/path
+            rr = wsk.apiexperimental.list(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            rr.stdout should include("ok: APIs")
+            rr.stdout should include regex (s"${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+            rr.stdout should include(testbasepath + testrelpath)
+
+            // Recreate the API using a JSON swagger file
+            rr = wsk.apiexperimental.get(basepathOrApiName = Some(testbasepath))
+            val swaggerfile = File.createTempFile("api", ".json")
+            swaggerfile.deleteOnExit()
+            val bw = new BufferedWriter(new FileWriter(swaggerfile))
+            bw.write(rr.stdout)
+            bw.close()
+
+            // Delete API to that it can be recreated again using the generated swagger file
+            val deleteApiResult = wsk.apiexperimental.delete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+
+            // Create the API again, but use the swagger file this time
+            rr = wsk.apiexperimental.create(swagger = Some(swaggerfile.getAbsolutePath()))
+            rr.stdout should include("ok: created API")
+            val swaggerapiurl = rr.stdout.split("\n")(1)
+            println(s"apiurl: '${swaggerapiurl}'")
+
+            // Call the API URL and validate the results
+            val response = whisk.utils.retry({
+                val response = RestAssured.given().config(sslconfig).get(s"$swaggerapiurl?$urlqueryparam=$urlqueryvalue")
+                response.statusCode should be(200)
+                response
+            }, 5, Some(1.second))
+            val responseString = response.body.asString
+            println("URL invocation response: " + responseString)
+            responseString.parseJson.asJsObject.fields(urlqueryparam).convertTo[String] should be(urlqueryvalue)
+
+        } finally {
+            println("Deleting action: " + actionName)
+            val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+            println("Deleting API: " + testbasepath)
+            val finallydeleteApiResult = wsk.apiexperimental.delete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        }
+    }
+
+    behavior of "Wsk api"
+
+    it should s"create an API and successfully invoke that API" in {
+        val testName = "APIGW_HEALTHTEST1"
+        val testbasepath = "/" + testName + "_bp"
+        val testrelpath = "/path"
+        val testurlop = "get"
+        val testapiname = testName + " API Name"
+        val actionName = testName + "_echo"
+        val urlqueryparam = "name"
+        val urlqueryvalue = "test"
+
+        try {
+            println("cli user: " + cliuser + "; cli namespace: " + clinamespace)
+
+            // Create the action for the API.  It must be a "web-action" action.
+            val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+            wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+            // Create the API
+            var rr = wsk.api.create(
+                basepath = Some(testbasepath),
+                relpath = Some(testrelpath),
+                operation = Some(testurlop),
+                action = Some(actionName),
+                apiname = Some(testapiname),
+                cliCfgFile = Some(cliWskPropsFile.getCanonicalPath())
+            )
             rr.stdout should include("ok: created API")
             val apiurl = rr.stdout.split("\n")(1)
             println(s"apiurl: '${apiurl}'")
@@ -78,13 +178,21 @@ class ApiGwEndToEndTests extends FlatSpec with Matchers with RestUtil with TestH
             // ok: APIs
             // Action                            Verb             API Name  URL
             // /_//whisk.system/utils/echo          get  APIGW_HEALTHTEST1 API Name  http://172.17.0.1:9001/api/ab9082cd-ea8e-465a-8a65-b491725cc4ef/APIGW_HEALTHTEST1_bp/path
-            rr = wsk.api.list(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            rr = wsk.api.list(
+                basepathOrApiName = Some(testbasepath),
+                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)
 
             // Recreate the API using a JSON swagger file
-            rr = wsk.api.get(basepathOrApiName = Some(testbasepath))
+            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))
@@ -92,10 +200,17 @@ class ApiGwEndToEndTests extends FlatSpec with Matchers with RestUtil with TestH
             bw.close()
 
             // Delete API to that it can be recreated again using the generated swagger file
-            val deleteApiResult = wsk.api.delete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+            val deleteApiResult = wsk.api.delete(
+                basepathOrApiName = testbasepath,
+                expectedExitCode = DONTCARE_EXIT,
+                cliCfgFile = Some(cliWskPropsFile.getCanonicalPath())
+            )
 
             // Create the API again, but use the swagger file this time
-            rr = wsk.api.create(swagger = Some(swaggerfile.getAbsolutePath()))
+            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)
             println(s"apiurl: '${swaggerapiurl}'")
@@ -114,7 +229,11 @@ class ApiGwEndToEndTests extends FlatSpec with Matchers with RestUtil with TestH
             println("Deleting action: " + actionName)
             val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
             println("Deleting API: " + testbasepath)
-            val finallydeleteApiResult = wsk.api.delete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+            val finallydeleteApiResult = wsk.api.delete(
+                basepathOrApiName = testbasepath,
+                expectedExitCode = DONTCARE_EXIT,
+                cliCfgFile = Some(cliWskPropsFile.getCanonicalPath())
+            )
         }
     }
 }
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 eae638e..249292f 100644
--- a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
@@ -16,8 +16,11 @@
 
 package whisk.core.cli.test
 
+import java.io.File
 import java.time.Instant
 import scala.concurrent.duration._
+import spray.json._
+import spray.json.DefaultJsonProtocol._
 import org.junit.runner.RunWith
 import org.scalatest.BeforeAndAfterAll
 import org.scalatest.junit.JUnitRunner
@@ -28,6 +31,7 @@ import common.WhiskProperties
 import common.Wsk
 import common.WskAdmin
 import common.WskProps
+import common.WskPropsV2
 import common.WskTestHelpers
 
 /**
@@ -52,6 +56,9 @@ class ApiGwTests
     var clearedThrottleTime = Instant.now
     val maxActionsPerMin = WhiskProperties.getMaxActionInvokesPerMinute()
 
+    // Custom CLI properties file
+    val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+
     /**
       * Expected to be called before or after each CLI invocation
       * If number of CLI invocations in this suite have reached the throttle limit
@@ -60,8 +67,9 @@ class ApiGwTests
     def checkThrottle() = {
       // If the # CLI calls at the throttle limit, then wait enough time to avoid the CLI being blocked
       cliCallCount += 1
-      if ( cliCallCount > maxActionsPerMin ) {
-        println(s"Action invokes ${cliCallCount} exceeds per minute thottle limit of ${maxActionsPerMin}")
+      println(s"Action invokes ${cliCallCount}; per minute thottle limit ${maxActionsPerMin}")
+      if ( cliCallCount > (maxActionsPerMin - 10) ) {  // Allow for some margin...10 invocations
+        println(s"Action invokes ${cliCallCount} close to exceeding per minute thottle limit of ${maxActionsPerMin}")
         val waitedAlready = Duration.fromNanos(java.time.Duration.between(clearedThrottleTime, Instant.now).toNanos)
         settleThrottle(waitedAlready)
         cliCallCount = 0
@@ -81,6 +89,17 @@ class ApiGwTests
     }
 
     /*
+     * Create a CLI properties file for use by the tests
+     */
+    override def beforeAll() = {
+      //cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+      cliWskPropsFile.deleteOnExit()
+      val wskprops = WskPropsV2(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
      */
@@ -92,7 +111,7 @@ class ApiGwTests
       }
     }
 
-    def apiCreate(
+    def apiCreateExperimental(
       basepath: Option[String] = None,
       relpath: Option[String] = None,
       operation: Option[String] = None,
@@ -101,10 +120,10 @@ class ApiGwTests
       swagger: Option[String] = None,
       expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
         checkThrottle()
-        wsk.api.create(basepath, relpath, operation, action, apiname, swagger, expectedExitCode)
+        wsk.apiexperimental.create(basepath, relpath, operation, action, apiname, swagger, expectedExitCode)
     }
 
-    def apiList(
+    def apiListExperimental(
       basepathOrApiName: Option[String] = None,
       relpath: Option[String] = None,
       operation: Option[String] = None,
@@ -113,38 +132,83 @@ class ApiGwTests
       full: Option[Boolean] = None,
       expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
         checkThrottle()
-        wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, expectedExitCode)
+        wsk.apiexperimental.list(basepathOrApiName, relpath, operation, limit, since, full, expectedExitCode)
     }
 
-    def apiGet(
+    def apiGetExperimental(
       basepathOrApiName: Option[String] = None,
       full: Option[Boolean] = None,
       expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
         checkThrottle()
-        wsk.api.get(basepathOrApiName, full, expectedExitCode)
+        wsk.apiexperimental.get(basepathOrApiName, full, expectedExitCode)
     }
 
-    def apiDelete(
+    def apiDeleteExperimental(
       basepathOrApiName: String,
       relpath: Option[String] = None,
       operation: Option[String] = None,
       expectedExitCode: Int = SUCCESS_EXIT): RunResult = {
         checkThrottle()
-        wsk.api.delete(basepathOrApiName, relpath, operation, expectedExitCode)
+        wsk.apiexperimental.delete(basepathOrApiName, relpath, operation, expectedExitCode)
     }
 
-    behavior of "Wsk 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,
+      expectedExitCode: Int = SUCCESS_EXIT,
+      cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+        checkThrottle()
+        wsk.api.create(basepath, relpath, operation, action, apiname, swagger, expectedExitCode, cliCfgFile)
+    }
+
+    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,
+      expectedExitCode: Int = SUCCESS_EXIT,
+      cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+        checkThrottle()
+        wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, expectedExitCode, cliCfgFile)
+    }
+
+    def apiGet(
+      basepathOrApiName: Option[String] = None,
+      full: Option[Boolean] = None,
+      expectedExitCode: Int = SUCCESS_EXIT,
+      cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+        checkThrottle()
+        wsk.api.get(basepathOrApiName, full, expectedExitCode, cliCfgFile)
+    }
+
+    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)
+    }
+
+  behavior of "Wsk api-experimental"
 
     it should "reject an api commands with an invalid path parameter" in {
         val badpath = "badpath"
 
-        var rr = apiCreate(basepath = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), action = Some("action"), expectedExitCode = ANY_ERROR_EXIT)
+        var rr = apiCreateExperimental(basepath = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), action = Some("action"), expectedExitCode = ANY_ERROR_EXIT)
         rr.stderr should include (s"'${badpath}' must begin with '/'")
 
-        rr = apiDelete(basepathOrApiName = "/basepath", relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
+        rr = apiDeleteExperimental(basepathOrApiName = "/basepath", relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
         rr.stderr should include (s"'${badpath}' must begin with '/'")
 
-        rr = apiList(basepathOrApiName = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
+        rr = apiListExperimental(basepathOrApiName = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
         rr.stderr should include (s"'${badpath}' must begin with '/'")
     }
 
@@ -159,10 +223,10 @@ class ApiGwTests
       try {
         println("cli user: " + cliuser + "; cli namespace: " + clinamespace)
 
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         println("api create: " + rr.stdout)
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), full = Some(true))
+        rr = apiListExperimental(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")
@@ -174,7 +238,7 @@ class ApiGwTests
         rr.stdout should include(testbasepath + testrelpath)
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath)
       }
     }
 
@@ -189,17 +253,17 @@ class ApiGwTests
         try {
             println("cli user: "+cliuser+"; cli namespace: "+clinamespace)
 
-            var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+            var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
             rr.stdout should include("ok: created API")
-            rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+            rr = apiListExperimental(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)
-            val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+            val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath)
             deleteresult.stdout should include("ok: deleted API")
         }
         finally {
-            val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+            val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
         }
     }
 
@@ -212,14 +276,14 @@ class ApiGwTests
         val testapiname = testName+" API Name"
         val actionName = testName+"_action"
         try {
-            var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+            var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
             rr.stdout should include("ok: created API")
-            rr = apiGet(basepathOrApiName = Some(testapiname))
+            rr = apiGetExperimental(basepathOrApiName = Some(testapiname))
             rr.stdout should include(testbasepath)
             rr.stdout should include(s"${actionName}")
         }
         finally {
-            val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+            val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
         }
     }
 
@@ -232,13 +296,13 @@ class ApiGwTests
       val testapiname = testName+" API Name"
       val actionName = testName+"_action"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiDelete(basepathOrApiName = testapiname)
+        rr = apiDeleteExperimental(basepathOrApiName = testapiname)
         rr.stdout should include("ok: deleted API")
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -251,13 +315,13 @@ class ApiGwTests
       val testapiname = testName+" API Name"
       val actionName = testName+"_action"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiDelete(basepathOrApiName = testbasepath)
+        rr = apiDeleteExperimental(basepathOrApiName = testbasepath)
         rr.stdout should include("ok: deleted API")
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -271,18 +335,18 @@ class ApiGwTests
       val actionName = testName+"_action"
       val newEndpoint = "/newEndpoint"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiCreate(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath))
+        rr = apiListExperimental(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)
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -296,9 +360,9 @@ class ApiGwTests
       val actionName = testName+"_action"
       val swaggerPath = TestUtils.getTestApiGwFilename("testswaggerdoc1")
       try {
-        var rr = apiCreate(swagger = Some(swaggerPath))
+        var rr = apiCreateExperimental(swagger = Some(swaggerPath))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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")
@@ -307,7 +371,7 @@ class ApiGwTests
         rr.stdout should include(testbasepath + testrelpath)
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -322,32 +386,32 @@ class ApiGwTests
       val actionName = testName+"_action"
       val newEndpoint = "/newEndpoint"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        rr = apiCreateExperimental(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
 
         // Update both APIs - each with a new endpoint
-        rr = apiCreate(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
+        rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
         rr.stdout should include("ok: created API")
-        rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
+        rr = apiCreateExperimental(basepath = Some(testbasepath2), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
         rr.stdout should include("ok: created API")
 
-        rr = apiList(basepathOrApiName = Some(testbasepath))
+        rr = apiListExperimental(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)
 
-        rr = apiList(basepathOrApiName = Some(testbasepath2))
+        rr = apiListExperimental(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)
       }
       finally {
-        var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-        deleteresult = apiDelete(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
+        var deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -364,17 +428,17 @@ class ApiGwTests
       try {
         println("cli user: "+cliuser+"; cli namespace: "+clinamespace)
 
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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)
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath)
         deleteresult.stdout should include("ok: deleted API")
       }
       finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -388,12 +452,12 @@ class ApiGwTests
       val actionName = testName + "_action"
       val swaggerPath = TestUtils.getTestApiGwFilename(s"testswaggerdocinvalid")
       try {
-        var rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
+        var rr = apiCreateExperimental(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")
       } finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -406,18 +470,18 @@ class ApiGwTests
       val testapiname = testName + " API Name"
       val actionName = testName + "_action"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        var rr2 = apiCreate(basepath = Some(testbasepath), relpath = Some(testnewrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr2 = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testnewrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr2.stdout should include("ok: created API")
-        rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath))
+        rr = apiDeleteExperimental(basepathOrApiName = testbasepath, relpath = Some(testrelpath))
         rr.stdout should include("ok: deleted " + testrelpath +" from "+ testbasepath)
-        rr2 = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testnewrelpath))
+        rr2 = apiListExperimental(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)
       } finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -431,20 +495,20 @@ class ApiGwTests
       val testapiname = testName + " API Name"
       val actionName = testName + "_action"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop2), action = Some(actionName), apiname = Some(testapiname))
+        rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop2), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath))
+        rr = apiListExperimental(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 = apiDelete(basepathOrApiName = testbasepath,relpath = Some(testrelpath), operation = Some(testurlop2))
+        rr = apiDeleteExperimental(basepathOrApiName = testbasepath,relpath = Some(testrelpath), operation = Some(testurlop2))
         rr.stdout should include("ok: deleted " + testrelpath + " " + "POST" +" from "+ testbasepath)
-        rr = apiList(basepathOrApiName = Some(testbasepath))
+        rr = apiListExperimental(basepathOrApiName = Some(testbasepath))
         rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
       } finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -458,18 +522,18 @@ class ApiGwTests
       val actionName = "test1a"
       val swaggerPath = TestUtils.getTestApiGwFilename(s"testswaggerdoc2")
       try {
-        var rr = apiCreate(swagger = Some(swaggerPath))
+        var rr = apiCreateExperimental(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), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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)
       } finally {
-        val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        val deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
       }
     }
 
@@ -483,29 +547,498 @@ class ApiGwTests
       val testapiname = testName + " API Name"
       val actionName = testName + "_action"
       try {
-        var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        var rr = apiCreateExperimental(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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)
-        rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+        rr = apiCreateExperimental(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
         rr.stdout should include("ok: created API")
-        rr = apiList(basepathOrApiName = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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)
-        rr = apiDelete(basepathOrApiName = testbasepath2)
+        rr = apiDeleteExperimental(basepathOrApiName = testbasepath2)
         rr.stdout should include("ok: deleted API")
-        rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
+        rr = apiListExperimental(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)
-        rr = apiDelete(basepathOrApiName = testbasepath)
+        rr = apiDeleteExperimental(basepathOrApiName = testbasepath)
         rr.stdout should include("ok: deleted API")
       } finally {
-        var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-        deleteresult = apiDelete(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
+        var deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+        deleteresult = apiDeleteExperimental(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
       }
     }
+
+  behavior of "Wsk api"
+
+  it should "reject an api commands with an invalid path parameter" in {
+    val badpath = "badpath"
+
+    var rr = apiCreate(basepath = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), action = Some("action"), expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include (s"'${badpath}' must begin with '/'")
+
+    rr = apiDelete(basepathOrApiName = "/basepath", relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include (s"'${badpath}' must begin with '/'")
+
+    rr = apiList(basepathOrApiName = Some("/basepath"), relpath = Some(badpath), operation = Some("GET"), expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include (s"'${badpath}' must begin with '/'")
+  }
+
+  it should "verify full list output" in {
+    val testName = "CLI_APIGWTEST_RO1"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    try {
+      println("cli user: " + cliuser + "; 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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      println("api create: " + rr.stdout)
+      rr.stdout should include("ok: created API")
+      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)
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+    }
+  }
+
+  it should "verify successful creation and deletion of a new API" in {
+    val testName = "CLI_APIGWTEST1"
+    val testbasepath = "/"+testName+"_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName+" API Name"
+    val actionName = testName+"_action"
+    try {
+      println("cli user: "+cliuser+"; 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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+      deleteresult.stdout should include("ok: deleted API")
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify get API name " in {
+    val testName = "CLI_APIGWTEST3"
+    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 be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiGet(basepathOrApiName = Some(testapiname))
+      rr.stdout should include(testbasepath)
+      rr.stdout should include(s"${actionName}")
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify delete API name " in {
+    val testName = "CLI_APIGWTEST4"
+    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 be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiDelete(basepathOrApiName = testapiname)
+      rr.stdout should include("ok: deleted API")
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify delete API basepath " in {
+    val testName = "CLI_APIGWTEST5"
+    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 be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiDelete(basepathOrApiName = testbasepath)
+      rr.stdout should include("ok: deleted API")
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify adding endpoints to existing api" in {
+    val testName = "CLI_APIGWTEST6"
+    val testbasepath = "/"+testName+"_bp"
+    val testrelpath = "/path2"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName+" API Name"
+    val actionName = testName+"_action"
+    val newEndpoint = "/newEndpoint"
+    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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiCreate(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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)
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify successful creation with swagger doc as input" in {
+    // NOTE: These values must match the swagger file contents
+    val testName = "CLI_APIGWTEST7"
+    val testbasepath = "/"+testName+"_bp"
+    val testrelpath = "/path"
+    val testurlop = "get"
+    val testapiname = testName+" API Name"
+    val actionName = testName+"_action"
+    val swaggerPath = TestUtils.getTestApiGwFilename("testswaggerdoc1V2")
+    try {
+      var rr = apiCreate(swagger = Some(swaggerPath))
+      rr.stdout should include("ok: created API")
+      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)
+    }
+    finally {
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify adding endpoints to two existing apis" in {
+    val testName = "CLI_APIGWTEST8"
+    val testbasepath = "/"+testName+"_bp"
+    val testbasepath2 = "/"+testName+"_bp2"
+    val testrelpath = "/path2"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName+" API Name"
+    val actionName = testName+"_action"
+    val newEndpoint = "/newEndpoint"
+    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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+
+      // Update both APIs - each with a new endpoint
+      rr = apiCreate(basepath = Some(testbasepath), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
+      rr.stdout should include("ok: created API")
+      rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(newEndpoint), operation = Some(testurlop), action = Some(actionName))
+      rr.stdout should include("ok: created API")
+
+      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)
+
+      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)
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+      deleteresult = apiDelete(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify successful creation of a new API using an action name using all allowed characters" in {
+    // Be aware: full action name is close to being truncated by the 'list' command
+    // e.g. /lime@us.ibm.com/CLI_APIGWTEST9a-c@t ion  is currently at the 40 char 'list' display max
+    val testName = "CLI_APIGWTEST9"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName+" API Name"
+    val actionName = testName+"a-c@t ion"
+    try {
+      println("cli user: "+cliuser+"; 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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath)
+      deleteresult.stdout should include("ok: deleted API")
+    }
+    finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify failed creation with invalid swagger doc as input" in {
+    val testName = "CLI_APIGWTEST10"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    val swaggerPath = TestUtils.getTestApiGwFilename(s"testswaggerdocinvalid")
+    try {
+      var 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")
+    } finally {
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify delete basepath/path " in {
+    val testName = "CLI_APIGWTEST11"
+    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 be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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")
+      rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath))
+      rr.stdout should include("ok: deleted " + testrelpath +" from "+ 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)
+    } finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify delete single operation from existing API basepath/path/operation(s) " in {
+    val testName = "CLI_APIGWTEST12"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path2"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testurlop2 = "post"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    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, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop2), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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 = apiDelete(basepathOrApiName = testbasepath,relpath = Some(testrelpath), operation = Some(testurlop2))
+      rr.stdout should include("ok: deleted " + testrelpath + " " + "POST" +" from "+ testbasepath)
+      rr = apiList(basepathOrApiName = Some(testbasepath))
+      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+    } finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify successful creation with complex swagger doc as input" in {
+    val testName = "CLI_APIGWTEST13"
+    val testbasepath = "/test1/v1"
+    val testrelpath = "/whisk_system/utils/echo"
+    val testrelpath2 = "/whisk_system/utils/split"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = "test1a"
+    val swaggerPath = TestUtils.getTestApiGwFilename(s"testswaggerdoc2V2")
+    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)
+      rr.stdout should include(testbasepath + testrelpath2)
+    } finally {
+      val deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "verify successful creation and deletion with multiple base paths" in {
+    val testName = "CLI_APIGWTEST14"
+    val testbasepath = "/" + testName + "_bp"
+    val testbasepath2 = "/" + testName + "_bp2"
+    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 be a "web-action" action.
+      val file = TestUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, annotations = Map("web-export" -> true.toJson))
+
+      var rr = apiCreate(basepath = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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)
+      rr = apiCreate(basepath = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop), action = Some(actionName), apiname = Some(testapiname))
+      rr.stdout should include("ok: created API")
+      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)
+      rr = apiDelete(basepathOrApiName = testbasepath2)
+      rr.stdout should include("ok: deleted API")
+      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)
+      rr = apiDelete(basepathOrApiName = testbasepath)
+      rr.stdout should include("ok: deleted API")
+    } finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+      deleteresult = apiDelete(basepathOrApiName = testbasepath2, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  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 {
+      var 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("API action does not exist")
+    }
+    finally {
+      val deleteresult = apiDeleteExperimental(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 testbasepath2 = "/" + testName + "_bp2"
+    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)
+
+      var 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("API action is not a web-action")
+    } finally {
+      val finallydeleteActionResult = wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      var deleteresult = apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
 }

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