From b0d311850438e550c0d8c98b68531d10f4d562b9 Mon Sep 17 00:00:00 2001
From: Rodric Rabbah <rabbah@us.ibm.com>
Date: Sat, 19 Nov 2016 14:07:17 -0500
Subject: [PATCH] Unify action container environments

Add npm openwhisk package to NodeJS images, and deprecation warning for uses of the 'whisk' context object.

Make OpenWhisk related environment variables available in all container runtimes.
---
 core/actionProxy/actionproxy.py               |  7 +-
 core/nodejsAction/Dockerfile                  |  1 +
 .../ActionProxyContainerTests.scala           | 58 +++++++++-----
 .../JavaActionContainerTests.scala            | 50 +++++++++++-
 .../NodeJsActionContainerTests.scala          | 80 +++++++++++++++++--
 .../PythonActionContainerTests.scala          |  9 ++-
 .../Swift3ActionContainerTests.scala          | 16 +---
 .../SwiftActionContainerTests.scala           | 37 ++++++---
 tests/src/system/basic/CLIPythonTests.scala   |  2 +-
 tests/src/system/basic/WskActionTests.scala   | 18 ++++-
 .../src/system/basic/WskBasicNodeTests.scala  |  4 +
 tests/src/system/basic/WskBasicTests.scala    | 16 ++--
 .../core/cli/test/WskBasicUsageTests.scala    | 59 ++++++++++++++
 13 files changed, 288 insertions(+), 69 deletions(-)

diff --git a/core/actionProxy/actionproxy.py b/core/actionProxy/actionproxy.py
index 4266f5c7..3c3c8400 100644
--- a/core/actionProxy/actionproxy.py
+++ b/core/actionProxy/actionproxy.py
@@ -91,13 +91,14 @@ class ActionRunner:
         return (os.path.isfile(self.binary) and os.access(self.binary, os.X_OK))
 
     # constructs an environment for the action to run in
-    # @param message is a JSON object received from invoker (should contain 'value' and 'authKey')
+    # @param message is a JSON object received from invoker (should contain 'value' and 'api_key' and other metadata)
     # @return an environment dictionary for the action process
     def env(self, message):
         # make sure to include all the env vars passed in by the invoker
         env = os.environ
-        if 'authKey' in message:
-            env['AUTH_KEY'] = message['authKey']
+        for p in [ 'api_key', 'namespace', 'action_name', 'activation_id', 'deadline' ]:
+             if p in message:
+                env['__OW_%s' % p.upper()] = message[p]
         return env
 
     # runs the action, called iff self.verify() is True.
diff --git a/core/nodejsAction/Dockerfile b/core/nodejsAction/Dockerfile
index 6af9760b..1abd89e9 100644
--- a/core/nodejsAction/Dockerfile
+++ b/core/nodejsAction/Dockerfile
@@ -34,6 +34,7 @@ mustache@2.1.3 \
 nano@5.10.0 \
 node-uuid@1.4.2 \
 oauth2-server@2.4.0 \
+openwhisk@2.6.0 \
 process@0.11.0 \
 request@2.79.0 \
 rimraf@2.5.1 \
diff --git a/tests/src/actionContainers/ActionProxyContainerTests.scala b/tests/src/actionContainers/ActionProxyContainerTests.scala
index 110b77b3..8584b85e 100644
--- a/tests/src/actionContainers/ActionProxyContainerTests.scala
+++ b/tests/src/actionContainers/ActionProxyContainerTests.scala
@@ -26,12 +26,8 @@ import org.scalatest.junit.JUnitRunner
 import ActionContainer.withContainer
 import common.TestUtils
 import common.WskActorSystem
-import spray.json.JsArray
-import spray.json.JsNull
-import spray.json.JsNumber
-import spray.json.JsObject
-import spray.json.JsString
-import spray.json.JsBoolean
+import spray.json.DefaultJsonProtocol._
+import spray.json._
 
 @RunWith(classOf[JUnitRunner])
 class ActionProxyContainerTests extends BasicActionRunnerTests with WskActorSystem {
@@ -81,20 +77,31 @@ class ActionProxyContainerTests extends BasicActionRunnerTests with WskActorSyst
     val stdEnvSamples = {
         val bash = """
                 |#!/bin/bash
-                |echo "{ \"auth\": \"$AUTH_KEY\", \"edge\": \"$EDGE_HOST\" }"
+                |echo "{ \
+                |\"api_host\": \"$__OW_API_HOST\", \"api_key\": \"$__OW_API_KEY\", \
+                |\"namespace\": \"$__OW_NAMESPACE\", \"action_name\": \"$__OW_ACTION_NAME\", \
+                |\"activation_id\": \"$__OW_ACTIVATION_ID\", \"deadline\": \"$__OW_DEADLINE\" }"
             """.stripMargin.trim
 
         val python = """
                 |#!/usr/bin/env python
                 |import os
-                |print '{ "auth": "%s", "edge": "%s" }' % (os.environ['AUTH_KEY'], os.environ['EDGE_HOST'])
+                |
+                |print '{ "api_host": "%s", "api_key": "%s", "namespace": "%s", "action_name" : "%s", "activation_id": "%s", "deadline": "%s" }' % (
+                |  os.environ['__OW_API_HOST'], os.environ['__OW_API_KEY'],
+                |  os.environ['__OW_NAMESPACE'], os.environ['__OW_ACTION_NAME'],
+                |  os.environ['__OW_ACTIVATION_ID'], os.environ['__OW_DEADLINE'])
             """.stripMargin.trim
 
         val perl = """
                 |#!/usr/bin/env perl
-                |$a = $ENV{'AUTH_KEY'};
-                |$e = $ENV{'EDGE_HOST'};
-                |print "{ \"auth\": \"$a\", \"edge\": \"$e\" }";
+                |$a = $ENV{'__OW_API_HOST'};
+                |$b = $ENV{'__OW_API_KEY'};
+                |$c = $ENV{'__OW_NAMESPACE'};
+                |$d = $ENV{'__OW_ACTION_NAME'};
+                |$e = $ENV{'__OW_ACTIVATION_ID'};
+                |$f = $ENV{'__OW_DEADLINE'};
+                |print "{ \"api_host\": \"$a\", \"api_key\": \"$b\", \"namespace\": \"$c\", \"action_name\": \"$d\", \"activation_id\": \"$e\", \"deadline\": \"$f\" }";
             """.stripMargin.trim
 
         // excluding perl as it not installed in alpine based image
@@ -244,28 +251,37 @@ trait BasicActionRunnerTests extends ActionProxyContainerTestUtils {
     }
 
     /** Runs tests for code samples which are expected to return the expected standard environment {auth, edge}. */
-    def testEnv(stdEnvSamples: Seq[(String, String)], enforceEmptyOutputStream: Boolean = true) = {
+    def testEnv(stdEnvSamples: Seq[(String, String)], enforceEmptyOutputStream: Boolean = true, enforceEmptyErrorStream: Boolean = true) = {
         for (s <- stdEnvSamples) {
             it should s"run a ${s._1} script and confirm expected environment variables" in {
-                val auth = JsString("abc")
-                val edge = "xyz"
-                val env = Map("EDGE_HOST" -> edge)
-
-                val (out, err) = withActionContainer(env) { c =>
+                val props = Seq(
+                    "api_host" -> "xyz",
+                    "api_key" -> "abc",
+                    "namespace" -> "zzz",
+                    "action_name" -> "xxx",
+                    "activation_id" -> "iii",
+                    "deadline" -> "123")
+                val env = props.map { case (k, v) => s"__OW_${k.toUpperCase()}" -> v }
+
+                val (out, err) = withActionContainer(env.take(1).toMap) { c =>
                     val (initCode, _) = c.init(initPayload(s._2))
                     initCode should be(200)
 
-                    val (runCode, out) = c.run(runPayload(JsObject(), Some(JsObject("authKey" -> auth))))
+                    val (runCode, out) = c.run(runPayload(JsObject(), Some(props.toMap.toJson.asJsObject)))
                     runCode should be(200)
                     out shouldBe defined
-                    out.get.fields("auth") shouldBe auth
-                    out.get.fields("edge") shouldBe JsString(edge)
+                    props.map {
+                        case (k, v) => withClue(k) {
+                            out.get.fields(k) shouldBe JsString(v)
+                        }
+
+                    }
                 }
 
                 checkStreams(out, err, {
                     case (o, e) =>
                         if (enforceEmptyOutputStream) o shouldBe empty
-                        e shouldBe empty
+                        if (enforceEmptyErrorStream) e shouldBe empty
                 })
             }
         }
diff --git a/tests/src/actionContainers/JavaActionContainerTests.scala b/tests/src/actionContainers/JavaActionContainerTests.scala
index 77bfff7a..876f0b4b 100644
--- a/tests/src/actionContainers/JavaActionContainerTests.scala
+++ b/tests/src/actionContainers/JavaActionContainerTests.scala
@@ -20,6 +20,7 @@ import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.Matchers
 import org.scalatest.junit.JUnitRunner
+import spray.json.DefaultJsonProtocol._
 import spray.json._
 
 import ActionContainer.withContainer
@@ -28,19 +29,62 @@ import ResourceHelpers.JarBuilder
 import common.WskActorSystem
 
 @RunWith(classOf[JUnitRunner])
-class JavaActionContainerTests extends FlatSpec with Matchers with WskActorSystem {
+class JavaActionContainerTests extends FlatSpec with Matchers with WskActorSystem with ActionProxyContainerTestUtils {
 
     // Helpers specific to javaaction
-    def withJavaContainer(code: ActionContainer => Unit) = withContainer("javaaction")(code)
+    def withJavaContainer(code: ActionContainer => Unit, env: Map[String, String] = Map.empty) = withContainer("javaaction", env)(code)
     def initPayload(mainClass: String, jar64: String) = JsObject(
         "value" -> JsObject(
             "name" -> JsString("dummyAction"),
             "main" -> JsString(mainClass),
             "jar" -> JsString(jar64)))
-    def runPayload(args: JsValue) = JsObject("value" -> args)
 
     behavior of "Java action"
 
+    it should s"run a java snippet and confirm expected environment variables" in {
+        val props = Seq("api_host" -> "xyz",
+            "api_key" -> "abc",
+            "namespace" -> "zzz",
+            "action_name" -> "xxx",
+            "activation_id" -> "iii",
+            "deadline" -> "123")
+        val env = props.map { case (k, v) => s"__OW_${k.toUpperCase}" -> v }
+        val (out, err) = withJavaContainer({ c =>
+            val jar = JarBuilder.mkBase64Jar(
+                Seq("example", "HelloWhisk.java") -> """
+                    | package example;
+                    |
+                    | import com.google.gson.JsonObject;
+                    |
+                    | public class HelloWhisk {
+                    |     public static JsonObject main(JsonObject args) {
+                    |         JsonObject response = new JsonObject();
+                    |         response.addProperty("api_host", System.getenv("__OW_API_HOST"));
+                    |         response.addProperty("api_key", System.getenv("__OW_API_KEY"));
+                    |         response.addProperty("namespace", System.getenv("__OW_NAMESPACE"));
+                    |         response.addProperty("action_name", System.getenv("__OW_ACTION_NAME"));
+                    |         response.addProperty("activation_id", System.getenv("__OW_ACTIVATION_ID"));
+                    |         response.addProperty("deadline", System.getenv("__OW_DEADLINE"));
+                    |         return response;
+                    |     }
+                    | }
+                """.stripMargin.trim)
+
+            val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar))
+            initCode should be(200)
+
+            val (runCode, out) = c.run(runPayload(JsObject(), Some(props.toMap.toJson.asJsObject)))
+            runCode should be(200)
+            props.map {
+                case (k, v) => out.get.fields(k) shouldBe JsString(v)
+
+            }
+        }, env.take(1).toMap)
+
+        out.trim shouldBe empty
+        err.trim shouldBe empty
+    }
+
     it should "support valid flows" in {
         val (out, err) = withJavaContainer { c =>
             val jar = JarBuilder.mkBase64Jar(
diff --git a/tests/src/actionContainers/NodeJsActionContainerTests.scala b/tests/src/actionContainers/NodeJsActionContainerTests.scala
index 395449da..f33e3e26 100644
--- a/tests/src/actionContainers/NodeJsActionContainerTests.scala
+++ b/tests/src/actionContainers/NodeJsActionContainerTests.scala
@@ -31,6 +31,8 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
     lazy val nodejsContainerImageName = "nodejsaction"
 
+    val hasDeprecationWarnings = true
+
     override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = {
         withContainer(nodejsContainerImageName, env)(code)
     }
@@ -68,6 +70,21 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
           """.stripMargin)
     })
 
+    testEnv(Seq {
+        ("node", """
+         |function main(args) {
+         |    return {
+         |       "api_host": process.env['__OW_API_HOST'],
+         |       "api_key": process.env['__OW_API_KEY'],
+         |       "namespace": process.env['__OW_NAMESPACE'],
+         |       "action_name": process.env['__OW_ACTION_NAME'],
+         |       "activation_id": process.env['__OW_ACTIVATION_ID'],
+         |       "deadline": process.env['__OW_DEADLINE']
+         |    }
+         |}
+         """.stripMargin.trim)
+    })
+
     it should "fail to initialize with bad code" in {
         val (out, err) = withNodeJsContainer { c =>
             val code = """
@@ -158,7 +175,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o shouldBe empty
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
         })
     }
 
@@ -180,7 +197,58 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o shouldBe empty
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
+        })
+    }
+
+    it should "warn when using deprecated whisk object methods" in {
+        val (out, err) = withNodeJsContainer { c =>
+            val code = """
+                | function main(args) {
+                |     whisk.getAuthKey(whisk.setAuthKey('xxx'));
+                |     try { whisk.invoke(); } catch (e) {}
+                |     try { whisk.trigger();  } catch (e) {}
+                |     setTimeout(function () { whisk.done(); }, 1000);
+                |     return whisk.async();
+                | }
+            """.stripMargin
+
+            c.init(initPayload(code))._1 should be(200)
+
+            val (runCode, runRes) = c.run(runPayload(JsObject()))
+            runCode should be(200)
+        }
+
+        checkStreams(out, err, {
+            case (o, e) =>
+                o shouldBe empty
+                e should not be empty
+                val lines = e.split("\n")
+                lines.filter { l => l.startsWith("[WARN] \"whisk.") && l.contains("deprecated") }.length shouldBe 8
+        })
+    }
+
+    it should "warn when using deprecated whisk.error" in {
+        val (out, err) = withNodeJsContainer { c =>
+            val code = """
+                | function main(args) {
+                |     whisk.error("{warnme: true}");
+                | }
+            """.stripMargin
+
+            c.init(initPayload(code))._1 should be(200)
+
+            val (runCode, runRes) = c.run(runPayload(JsObject()))
+            runCode should be(200)
+        }
+
+        checkStreams(out, err, {
+            case (o, e) =>
+                o shouldBe empty
+                e should not be empty
+                val lines = e.split("\n")
+                lines.length shouldBe 1
+                lines.forall { l => l.startsWith("[WARN] \"whisk.") && l.contains("deprecated") }
         })
     }
 
@@ -205,7 +273,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o should include("more than once")
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
         })
     }
 
@@ -243,7 +311,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o shouldBe empty
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
         }, 3)
     }
 
@@ -268,7 +336,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o shouldBe empty
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
         })
     }
 
@@ -302,7 +370,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys
         checkStreams(out, err, {
             case (o, e) =>
                 o shouldBe empty
-                e shouldBe empty
+                if (!hasDeprecationWarnings) e shouldBe empty
         }, 2)
     }
 
diff --git a/tests/src/actionContainers/PythonActionContainerTests.scala b/tests/src/actionContainers/PythonActionContainerTests.scala
index fe17476c..41968bf2 100644
--- a/tests/src/actionContainers/PythonActionContainerTests.scala
+++ b/tests/src/actionContainers/PythonActionContainerTests.scala
@@ -53,7 +53,14 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
         ("python", """
          |import os
          |def main(dict):
-         |    return { "auth": os.environ['AUTH_KEY'], "edge": os.environ['EDGE_HOST'] }
+         |    return {
+         |       "api_host": os.environ['__OW_API_HOST'],
+         |       "api_key": os.environ['__OW_API_KEY'],
+         |       "namespace": os.environ['__OW_NAMESPACE'],
+         |       "action_name": os.environ['__OW_ACTION_NAME'],
+         |       "activation_id": os.environ['__OW_ACTIVATION_ID'],
+         |       "deadline": os.environ['__OW_DEADLINE']
+         |    }
          """.stripMargin.trim)
     })
 
diff --git a/tests/src/actionContainers/Swift3ActionContainerTests.scala b/tests/src/actionContainers/Swift3ActionContainerTests.scala
index f1f5bbde..a294bea6 100644
--- a/tests/src/actionContainers/Swift3ActionContainerTests.scala
+++ b/tests/src/actionContainers/Swift3ActionContainerTests.scala
@@ -27,20 +27,8 @@ class Swift3ActionContainerTests extends SwiftActionContainerTests {
 
     override val enforceEmptyOutputStream = false
     override lazy val swiftContainerImageName = "swift3action"
-    override lazy val envCode =  """
-         |func main(args: [String: Any]) -> [String: Any] {
-         |     let env = ProcessInfo.processInfo.environment
-         |     var auth = "???"
-         |     var edge = "???"
-         |     if let authKey : String = env["AUTH_KEY"] {
-         |         auth = "\(authKey)"
-         |     }
-         |     if let edgeHost : String = env["EDGE_HOST"] {
-         |         edge = "\(edgeHost)"
-         |     }
-         |     return ["auth": auth, "edge": edge]
-         |}
-         """.stripMargin
+    override lazy val envCode = makeEnvCode("ProcessInfo.processInfo")
+
     override lazy val errorCode = """
                 | // You need an indirection, or swiftc detects the div/0
                 | // at compile-time. Smart.
diff --git a/tests/src/actionContainers/SwiftActionContainerTests.scala b/tests/src/actionContainers/SwiftActionContainerTests.scala
index 44d965e2..cfe3ce2a 100644
--- a/tests/src/actionContainers/SwiftActionContainerTests.scala
+++ b/tests/src/actionContainers/SwiftActionContainerTests.scala
@@ -32,21 +32,38 @@ class SwiftActionContainerTests extends BasicActionRunnerTests with WskActorSyst
     // prints status messages and there doesn't seem to be a way to quiet them
     val enforceEmptyOutputStream = true
     lazy val swiftContainerImageName = "swiftaction"
+    lazy val envCode = makeEnvCode("NSProcessInfo.processInfo()")
 
-    lazy val envCode = """
+    def makeEnvCode(processInfo: String) = ("""
          |func main(args: [String: Any]) -> [String: Any] {
-         |     let env = NSProcessInfo.processInfo().environment
-         |     var auth = "???"
-         |     var edge = "???"
-         |     if let authKey : String = env["AUTH_KEY"] {
-         |         auth = "\(authKey)"
+         |     let env = """+processInfo+""".environment
+         |     var a = "???"
+         |     var b = "???"
+         |     var c = "???"
+         |     var d = "???"
+         |     var e = "???"
+         |     var f = "???"
+         |     if let v : String = env["__OW_API_HOST"] {
+         |         a = "\(v)"
          |     }
-         |     if let edgeHost : String = env["EDGE_HOST"] {
-         |         edge = "\(edgeHost)"
+         |     if let v : String = env["__OW_API_KEY"] {
+         |         b = "\(v)"
          |     }
-         |     return ["auth": auth, "edge": edge]
+         |     if let v : String = env["__OW_NAMESPACE"] {
+         |         c = "\(v)"
+         |     }
+         |     if let v : String = env["__OW_ACTION_NAME"] {
+         |         d = "\(v)"
+         |     }
+         |     if let v : String = env["__OW_ACTIVATION_ID"] {
+         |         e = "\(v)"
+         |     }
+         |     if let v : String = env["__OW_DEADLINE"] {
+         |         f = "\(v)"
+         |     }
+         |     return ["api_host": a, "api_key": b, "namespace": c, "action_name": d, "activation_id": e, "deadline": f]
          |}
-         """.stripMargin
+         """).stripMargin
 
     lazy val errorCode = """
                 | // You need an indirection, or swiftc detects the div/0
diff --git a/tests/src/system/basic/CLIPythonTests.scala b/tests/src/system/basic/CLIPythonTests.scala
index 2f00ca64..891745bc 100644
--- a/tests/src/system/basic/CLIPythonTests.scala
+++ b/tests/src/system/basic/CLIPythonTests.scala
@@ -72,7 +72,7 @@ class CLIPythonTests
 
     it should "invoke an invalid action and get error back" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
-            val name = "basicInvoke"
+            val name = "bad code"
             assetHelper.withCleaner(wsk.action, name) {
                 (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("malformed.py")))
             }
diff --git a/tests/src/system/basic/WskActionTests.scala b/tests/src/system/basic/WskActionTests.scala
index 1cc8e8e0..524eb206 100644
--- a/tests/src/system/basic/WskActionTests.scala
+++ b/tests/src/system/basic/WskActionTests.scala
@@ -45,6 +45,22 @@ class WskActionTests
 
     behavior of "Whisk actions"
 
+    it should "invoke an action returning a promise" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            val name = "hello promise"
+            assetHelper.withCleaner(wsk.action, name) {
+                (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloPromise.js")))
+            }
+
+            val run = wsk.action.invoke(name)
+            withActivation(wsk.activation, run) {
+                activation =>
+                    activation.response.status shouldBe "success"
+                    activation.response.result shouldBe Some(JsObject("done" -> true.toJson))
+                    activation.logs.get.mkString(" ") shouldBe empty
+            }
+    }
+
     it should "invoke an action with a space in the name" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             val name = "hello Async"
@@ -169,7 +185,7 @@ class WskActionTests
             wsk.parseJsonString(rr.stdout).getFieldPath("exec", "code") shouldBe Some(JsString(""))
     }
 
-    it should "blocking invoke nested blocking actions" in withAssetCleaner(wskprops) {
+    it should "blocking invoke of nested blocking actions" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             val name = "nestedBlockingAction"
             val child = "wc"
diff --git a/tests/src/system/basic/WskBasicNodeTests.scala b/tests/src/system/basic/WskBasicNodeTests.scala
index 4e5fdca1..d78c7d62 100644
--- a/tests/src/system/basic/WskBasicNodeTests.scala
+++ b/tests/src/system/basic/WskBasicNodeTests.scala
@@ -113,6 +113,7 @@ class WskBasicNodeTests
             }
     }
 
+    // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary
     it should "Ensure that whisk.invoke() returns a promise" in withAssetCleaner(wskprops) {
         val expectedDuration = 3.seconds
 
@@ -165,6 +166,7 @@ class WskBasicNodeTests
             }
     }
 
+    // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary
     it should "Ensure that whisk.invoke() still uses a callback when provided one" in withAssetCleaner(wskprops) {
         val expectedDuration = 3.seconds
 
@@ -216,6 +218,7 @@ class WskBasicNodeTests
             }
     }
 
+    // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary
     it should "Ensure that whisk.trigger() still uses a callback when provided one" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             // this action supplies a 'next' callback to whisk.trigger()
@@ -251,6 +254,7 @@ class WskBasicNodeTests
             }
     }
 
+    // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary
     it should "Ensure that whisk.trigger() returns a promise" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             // this action supplies a 'next' callback to whisk.trigger()
diff --git a/tests/src/system/basic/WskBasicTests.scala b/tests/src/system/basic/WskBasicTests.scala
index 36c661dc..1acfa408 100644
--- a/tests/src/system/basic/WskBasicTests.scala
+++ b/tests/src/system/basic/WskBasicTests.scala
@@ -16,19 +16,15 @@
 
 package system.basic
 
-import java.time.Instant
 import java.io.File
+import java.time.Instant
 
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
 
 import common.TestHelpers
 import common.TestUtils
-import common.TestUtils.CONFLICT
-import common.TestUtils.SUCCESS_EXIT
-import common.TestUtils.UNAUTHORIZED
-import common.TestUtils.FORBIDDEN
-import common.TestUtils.ERROR_EXIT
+import common.TestUtils._
 import common.Wsk
 import common.WskProps
 import common.WskTestHelpers
@@ -361,15 +357,17 @@ class WskBasicTests
             }
     }
 
-    it should "create and invoke a blocking action resulting in an error response object" in withAssetCleaner(wskprops) {
+    it should "create and invoke a blocking action resulting in an failed promise" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             val name = "errorResponseObject"
             assetHelper.withCleaner(wsk.action, name) {
                 (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("asyncError.js")))
             }
 
-            wsk.action.invoke(name, blocking = true, expectedExitCode = 246)
-                .stderr should include regex (""""error": "name '!' contains illegal characters \(code \d+\)"""")
+            val stderr = wsk.action.invoke(name, blocking = true, expectedExitCode = 246).stderr
+            CliActivation.serdes.read(stderr.parseJson).response.result shouldBe Some {
+                JsObject("error" -> JsObject("msg" -> "failed activation on purpose".toJson))
+            }
     }
 
     it should "invoke a blocking action and get only the result" in withAssetCleaner(wskprops) {
diff --git a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
index e832c112..0bf4198b 100644
--- a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
+++ b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala
@@ -45,6 +45,8 @@ import whisk.core.entity.size.SizeInt
 import whisk.utils.retry
 import JsonArgsForTests._
 import whisk.http.Messages
+import common.WskAdmin
+import java.time.Clock
 
 /**
  * Tests for basic CLI usage. Some of these tests require a deployed backend.
@@ -452,6 +454,63 @@ class WskBasicUsageTests
             }
     }
 
+    it should "invoke an action using npm openwhisk" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            val name = "hello npm openwhisk"
+            assetHelper.withCleaner(wsk.action, name, confirmDelete = false) {
+                (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloOpenwhiskPackage.js")))
+            }
+
+            val run = wsk.action.invoke(name, Map("ignore_certs" -> true.toJson, "name" -> name.toJson))
+            withActivation(wsk.activation, run) {
+                activation =>
+                    activation.response.status shouldBe "success"
+                    activation.response.result shouldBe Some(JsObject("delete" -> true.toJson))
+                    activation.logs.get.mkString(" ") should include("action list has this many actions")
+            }
+
+            wsk.action.delete(name, expectedExitCode = TestUtils.NOT_FOUND)
+    }
+
+    it should "invoke an action receiving context properties" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            val user = WskAdmin.getUser(wskprops.authKey)
+            val name = "context"
+            assetHelper.withCleaner(wsk.action, name) {
+                (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloContext.js")))
+            }
+
+            val start = Instant.now(Clock.systemUTC()).toEpochMilli
+            val run = wsk.action.invoke(name)
+            withActivation(wsk.activation, run) {
+                activation =>
+                    activation.response.status shouldBe "success"
+                    val fields = activation.response.result.get.convertTo[Map[String, String]]
+                    fields("api_host") shouldBe WhiskProperties.getEdgeHost + ":" + WhiskProperties.getEdgeHostApiPort
+                    fields("api_key") shouldBe wskprops.authKey
+                    fields("namespace") shouldBe user
+                    fields("action_name") shouldBe s"/$user/$name"
+                    fields("activation_id") shouldBe activation.activationId
+                    fields("deadline").toLong should be >= start
+            }
+    }
+
+    it should s"invoke an action that returns a result by the deadline" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            val name = "deadline"
+            assetHelper.withCleaner(wsk.action, name) {
+                (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloDeadline.js")), timeout = Some(3 seconds))
+            }
+
+            val start = Instant.now(Clock.systemUTC()).toEpochMilli
+            val run = wsk.action.invoke(name)
+            withActivation(wsk.activation, run) {
+                activation =>
+                    activation.response.status shouldBe "success"
+                    activation.response.result shouldBe Some(JsObject("timedout" -> true.toJson))
+            }
+    }
+
     behavior of "Wsk packages"
 
     it should "create, and get a package to verify parameter and annotation parsing" in withAssetCleaner(wskprops) {
-- 
GitLab