blob: 1c76ce508f6f7ced309fbb9997b109cc44e9f8f7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package system.basic
import java.time.Instant
import scala.concurrent.duration.DurationInt
import scala.language.postfixOps
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import common.ActivationResult
import common.TestHelpers
import common.TestUtils
import common.TestUtils._
import common.Wsk
import common.WskProps
import common.WskTestHelpers
import spray.json._
import spray.json.DefaultJsonProtocol._
import org.apache.openwhisk.http.Messages
@RunWith(classOf[JUnitRunner])
class WskCliBasicTests extends TestHelpers with WskTestHelpers {
implicit val wskprops = WskProps()
val wsk = new Wsk
val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
behavior of "Wsk CLI"
it should "reject creating duplicate entity" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "testDuplicateCreate"
assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
action.create(name, defaultAction, expectedExitCode = CONFLICT)
}
}
it should "reject deleting entity in wrong collection" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "testCrossDelete"
assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
trigger.create(name)
}
wsk.action.delete(name, expectedExitCode = CONFLICT)
}
val WskCLI_RejUnauthAccess_exitCode = UNAUTHORIZED
val WskCLI_RejUnauthAccess_stderr = "The supplied authentication is invalid"
it should "reject unauthenticated access" in {
implicit val new_wskprops = wskprops.copy(authKey = "xxx") //WskProps("xxx") // shadow properties
wsk.namespace.get(expectedExitCode = WskCLI_RejUnauthAccess_exitCode)(new_wskprops).stderr should include(
WskCLI_RejUnauthAccess_stderr)
}
behavior of "Wsk Package CLI"
it should "create, update, get and list a package" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "testPackage"
val params = Map("a" -> "A".toJson)
assetHelper.withCleaner(wsk.pkg, name) { (pkg, _) =>
pkg.create(name, parameters = params, shared = Some(true))
pkg.create(name, update = true)
}
val stdout = wsk.pkg.get(name).stdout
stdout should include regex (""""key": "a"""")
stdout should include regex (""""value": "A"""")
stdout should include regex (""""publish": true""")
stdout should include regex (""""version": "0.0.2"""")
wsk.pkg.list().stdout should include(name)
}
it should "create, and get a package summary" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val packageName = "packageName"
val actionName = "actionName"
val packageAnnots = Map(
"description" -> JsString("Package description"),
"parameters" -> JsArray(
JsObject("name" -> JsString("paramName1"), "description" -> JsString("Parameter description 1")),
JsObject("name" -> JsString("paramName2"), "description" -> JsString("Parameter description 2"))))
val actionAnnots = Map(
"description" -> JsString("Action description"),
"parameters" -> JsArray(
JsObject("name" -> JsString("paramName1"), "description" -> JsString("Parameter description 1")),
JsObject("name" -> JsString("paramName2"), "description" -> JsString("Parameter description 2"))))
assetHelper.withCleaner(wsk.pkg, packageName) { (pkg, _) =>
pkg.create(packageName, annotations = packageAnnots)
}
wsk.action.create(packageName + "/" + actionName, defaultAction, annotations = actionAnnots)
val stdout = wsk.pkg.get(packageName, summary = true).stdout
val ns = wsk.namespace.whois()
wsk.action.delete(packageName + "/" + actionName)
stdout should include regex (s"(?i)package /$ns/$packageName: Package description\\s*\\(parameters: paramName1, paramName2\\)")
stdout should include regex (s"(?i)action /$ns/$packageName/$actionName: Action description\\s*\\(parameters: paramName1, paramName2\\)")
}
it should "create a package with a name that contains spaces" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "package with spaces"
val res = assetHelper.withCleaner(wsk.pkg, name) { (pkg, _) =>
pkg.create(name)
}
res.stdout should include(s"ok: created package $name")
}
it should "create a package, and get its individual fields" in withAssetCleaner(wskprops) {
val name = "packageFields"
val paramInput = Map("payload" -> "test".toJson)
val successMsg = s"ok: got package $name, displaying field"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.pkg, name) { (pkg, _) =>
pkg.create(name, parameters = paramInput)
}
val expectedParam = JsObject("payload" -> JsString("test"))
val ns = wsk.namespace.whois()
wsk.pkg
.get(name, fieldFilter = Some("namespace"))
.stdout should include regex (s"""(?i)$successMsg namespace\n"$ns"""")
wsk.pkg.get(name, fieldFilter = Some("name")).stdout should include(s"""$successMsg name\n"$name"""")
wsk.pkg.get(name, fieldFilter = Some("version")).stdout should include(s"""$successMsg version\n"0.0.1"""")
wsk.pkg.get(name, fieldFilter = Some("publish")).stdout should include(s"""$successMsg publish\nfalse""")
wsk.pkg.get(name, fieldFilter = Some("binding")).stdout should include regex (s"""\\{\\}""")
wsk.pkg.get(name, fieldFilter = Some("invalid"), expectedExitCode = ERROR_EXIT).stderr should include(
"error: Invalid field filter 'invalid'.")
}
it should "reject creation of duplication packages" in withAssetCleaner(wskprops) {
val name = "dupePackage"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.pkg, name) { (pkg, _) =>
pkg.create(name)
}
val stderr = wsk.pkg.create(name, expectedExitCode = CONFLICT).stderr
stderr should include regex (s"""Unable to create package '$name': resource already exists \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject delete of package that does not exist" in {
val name = "nonexistentPackage"
val stderr = wsk.pkg.delete(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to delete package '$name'. The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject get of package that does not exist" in {
val name = "nonexistentPackage"
val ns = wsk.namespace.whois()
val stderr = wsk.pkg.get(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get package '$name': ${Messages.resourceDoesntExist(s"${ns}/${name}")} \\(code [0-9a-zA-Z_-]+\\)""")
}
behavior of "Wsk Action CLI"
it should "create the same action twice with different cases" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, "TWICE") { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.action, "twice") { (action, name) =>
action.create(name, defaultAction)
}
}
it should "create, update, get and list an action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "createAndUpdate"
val file = Some(TestUtils.getTestActionFilename("hello.js"))
val params = Map("a" -> "A".toJson)
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, file, parameters = params)
action.create(name, None, parameters = Map("b" -> "B".toJson), update = true)
}
val stdout = wsk.action.get(name).stdout
stdout should not include regex(""""key": "a"""")
stdout should not include regex(""""value": "A"""")
stdout should include regex (""""key": "b""")
stdout should include regex (""""value": "B"""")
stdout should include regex (""""publish": false""")
stdout should include regex (""""version": "0.0.2"""")
wsk.action.list().stdout should include(name)
}
it should "reject create of an action that already exists" in withAssetCleaner(wskprops) {
val name = "dupeAction"
val file = Some(TestUtils.getTestActionFilename("echo.js"))
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, file)
}
val stderr = wsk.action.create(name, file, expectedExitCode = CONFLICT).stderr
stderr should include regex (s"""Unable to create action '$name': resource already exists \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject delete of action that does not exist" in {
val name = "nonexistentAction"
val stderr = wsk.action.delete(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to delete action '$name'. The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject invocation of action that does not exist" in {
val name = "nonexistentAction"
val stderr = wsk.action.invoke(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to invoke action '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject get of an action that does not exist" in {
val name = "nonexistentAction"
val stderr = wsk.action.get(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get action '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "create, and invoke an action that utilizes a docker container" in withAssetCleaner(wskprops) {
val name = "dockerContainer"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) {
// this docker image will be need to be pulled from dockerhub and hence has to be published there first
(action, _) =>
action.create(name, None, docker = Some("openwhisk/example"))
}
val args = Map("payload" -> "test".toJson)
val run = wsk.action.invoke(name, args)
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(
JsObject("args" -> args.toJson, "msg" -> "Hello from arbitrary C program!".toJson))
}
}
it should "create, and invoke an action that utilizes dockerskeleton with native zip" in withAssetCleaner(wskprops) {
val name = "dockerContainerWithZip"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) {
// this docker image will be need to be pulled from dockerhub and hence has to be published there first
(action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("blackbox.zip")), kind = Some("native"))
}
val run = wsk.action.invoke(name, Map())
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(JsObject("msg" -> "hello zip".toJson))
activation.logs shouldBe defined
val logs = activation.logs.get.toString
logs should include("This is an example zip used with the docker skeleton action.")
logs should not include ("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
}
}
it should "create, and invoke an action using a parameter file" in withAssetCleaner(wskprops) {
val name = "paramFileAction"
val file = Some(TestUtils.getTestActionFilename("argCheck.js"))
val argInput = Some(TestUtils.getTestActionFilename("validInput2.json"))
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, file)
}
val expectedOutput = JsObject("payload" -> JsString("test"))
val run = wsk.action.invoke(name, parameterFile = argInput)
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(expectedOutput)
}
}
it should "create an action, and get its individual fields" in withAssetCleaner(wskprops) {
val name = "actionFields"
val paramInput = Map("payload" -> "test".toJson)
val successMsg = s"ok: got action $name, displaying field"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, defaultAction, parameters = paramInput)
}
val expectedParam = JsObject("payload" -> JsString("test"))
val ns = wsk.namespace.whois()
wsk.action.get(name, fieldFilter = Some("name")).stdout should include(s"""$successMsg name\n"$name"""")
wsk.action.get(name, fieldFilter = Some("version")).stdout should include(s"""$successMsg version\n"0.0.1"""")
wsk.action.get(name, fieldFilter = Some("exec")).stdout should include(successMsg)
wsk.action
.get(name, fieldFilter = Some("exec"))
.stdout should include regex (s"""$successMsg exec\n\\{\\s+"kind":\\s+"nodejs:6",\\s+"binary":\\s+false\\s+\\}""")
wsk.action
.get(name, fieldFilter = Some("parameters"))
.stdout should include regex (s"""$successMsg parameters\n\\[\\s+\\{\\s+"key":\\s+"payload",\\s+"value":\\s+"test"\\s+\\}\\s+\\]""")
wsk.action
.get(name, fieldFilter = Some("annotations"))
.stdout should include regex (s"""$successMsg annotations\n\\[\\s+\\{\\s+"key":\\s+"provide-api-key",\\s+"value":\\s+false\\s+\\},\\s+\\{\\s+"key":\\s+"exec",\\s+"value":\\s+"nodejs:6"\\s+\\}\\s+\\]""")
wsk.action
.get(name, fieldFilter = Some("limits")) //
.stdout should include regex (s"""$successMsg limits\n\\{\\s+"timeout":\\s+60000,\\s+"memory":\\s+256,\\s+"logs":\\s+10,\\s+"concurrency":\\s+1\\s+\\}""")
wsk.action
.get(name, fieldFilter = Some("namespace"))
.stdout should include regex (s"""(?i)$successMsg namespace\n"$ns"""")
wsk.action.get(name, fieldFilter = Some("invalid"), expectedExitCode = MISUSE_EXIT).stderr should include(
"error: Invalid field filter 'invalid'.")
wsk.action.get(name, fieldFilter = Some("publish")).stdout should include(s"""$successMsg publish\nfalse""")
}
/**
* Tests creating an action from a malformed js file. This should fail in
* some way - preferably when trying to create the action. If not, then
* surely when it runs there should be some indication in the logs. Don't
* think this is true currently.
*/
it should "create and invoke action with malformed js resulting in activation error" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "MALFORMED"
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("malformed.js")))
}
val run = wsk.action.invoke(name, Map("payload" -> "whatever".toJson))
withActivation(wsk.activation, run) { activation =>
activation.response.status shouldBe "action developer error"
// representing nodejs giving an error when given malformed.js
activation.response.result.get.toString should include("ReferenceError")
}
}
it should "create and invoke a blocking action resulting in an application error response" in withAssetCleaner(
wskprops) { (wp, assetHelper) =>
val name = "applicationError"
val strErrInput = Map("error" -> "Error message".toJson)
val numErrInput = Map("error" -> 502.toJson)
val boolErrInput = Map("error" -> true.toJson)
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("echo.js")))
}
Seq(strErrInput, numErrInput, boolErrInput) foreach { input =>
getJSONFromResponse(
wsk.action.invoke(name, parameters = input, blocking = true, expectedExitCode = 246).stderr,
wsk.isInstanceOf[Wsk])
.fields("response")
.asJsObject
.fields("result")
.asJsObject shouldBe input.toJson.asJsObject
wsk.action
.invoke(name, parameters = input, blocking = true, result = true, expectedExitCode = 246)
.stderr
.parseJson
.asJsObject shouldBe input.toJson.asJsObject
}
}
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")))
}
val stderr = wsk.action.invoke(name, blocking = true, expectedExitCode = 246).stderr
ActivationResult.serdes.read(removeCLIHeader(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) { (wp, assetHelper) =>
val name = "basicInvoke"
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("wc.js")))
}
wsk.action
.invoke(name, Map("payload" -> "one two three".toJson), result = true)
.stdout should include regex (""""count": 3""")
}
it should "create, and get an action summary" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "actionName"
val annots = Map(
"description" -> JsString("Action description"),
"parameters" -> JsArray(
JsObject("name" -> JsString("paramName1"), "description" -> JsString("Parameter description 1")),
JsObject("name" -> JsString("paramName2"), "description" -> JsString("Parameter description 2"))))
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, defaultAction, annotations = annots)
}
val stdout = wsk.action.get(name, summary = true).stdout
val ns = wsk.namespace.whois()
stdout should include regex (s"(?i)action /$ns/$name: Action description\\s*\\(parameters: paramName1, paramName2\\)")
}
it should "create an action with a name that contains spaces" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "action with spaces"
val res = assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, defaultAction)
}
res.stdout should include(s"ok: created action $name")
}
it should "create an action, and invoke an action that returns an empty JSON object" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val name = "emptyJSONAction"
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("emptyJSONResult.js")))
}
val stdout = wsk.action.invoke(name, result = true).stdout
stdout.parseJson.asJsObject shouldBe JsObject()
}
it should "create, and invoke an action that times out to ensure the proper response is received" in withAssetCleaner(
wskprops) { (wp, assetHelper) =>
val name = "sleepAction"
val params = Map("sleepTimeInMs" -> 100000.toJson)
val allowedActionDuration = 120 seconds
val res = assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, Some(TestUtils.getTestActionFilename("sleep.js")), timeout = Some(allowedActionDuration))
action.invoke(name, parameters = params, result = true, expectedExitCode = ACCEPTED)
}
res.stderr should include("""but the request has not yet finished""")
}
it should "create, and get docker action get ensure exec code is omitted" in withAssetCleaner(wskprops) {
val name = "dockerContainer"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
action.create(name, None, docker = Some("fake-container"))
}
wsk.action.get(name).stdout should not include (""""code"""")
}
behavior of "Wsk Trigger CLI"
it should "create, update, get, fire and list trigger" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val ruleName = withTimestamp("r1toa1")
val triggerName = withTimestamp("t1tor1")
val actionName = withTimestamp("a1")
val params = Map("a" -> "A".toJson)
val ns = wsk.namespace.whois()
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
trigger.create(triggerName, parameters = params)
trigger.create(triggerName, update = true)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val trigger = wsk.trigger.get(triggerName)
getJSONFromResponse(trigger.stdout, true).fields("parameters") shouldBe JsArray(
JsObject("key" -> JsString("a"), "value" -> JsString("A")))
getJSONFromResponse(trigger.stdout, true).fields("publish") shouldBe false.toJson
getJSONFromResponse(trigger.stdout, true).fields("version") shouldBe "0.0.2".toJson
val expectedRules = JsObject(
ns + "/" + ruleName -> JsObject(
"action" -> JsObject("name" -> JsString(actionName), "path" -> JsString(ns)),
"status" -> JsString("active")))
getJSONFromResponse(trigger.stdout, true).fields("rules") shouldBe expectedRules
val dynamicParams = Map("t" -> "T".toJson)
val run = wsk.trigger.fire(triggerName, dynamicParams)
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(dynamicParams.toJson)
activation.duration shouldBe 0L // shouldn't exist but CLI generates it
activation.end shouldBe Instant.EPOCH // shouldn't exist but CLI generates it
activation.logs shouldBe defined
activation.logs.get.size shouldBe 1
val logEntry = activation.logs.get(0).parseJson.asJsObject
val logs = JsArray(logEntry)
val ruleActivationId: String = logEntry.fields("activationId").convertTo[String]
val expectedLogs = JsArray(
JsObject(
"statusCode" -> JsNumber(0),
"activationId" -> JsString(ruleActivationId),
"success" -> JsBoolean(true),
"rule" -> JsString(ns + "/" + ruleName),
"action" -> JsString(ns + "/" + actionName)))
logs shouldBe expectedLogs
}
val runWithNoParams = wsk.trigger.fire(triggerName, Map())
withActivation(wsk.activation, runWithNoParams) { activation =>
activation.response.result shouldBe Some(JsObject())
activation.duration shouldBe 0L // shouldn't exist but CLI generates it
activation.end shouldBe Instant.EPOCH // shouldn't exist but CLI generates it
}
wsk.trigger.list().stdout should include(triggerName)
}
it should "create, and get a trigger summary" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "triggerName"
val annots = Map(
"description" -> JsString("Trigger description"),
"parameters" -> JsArray(
JsObject("name" -> JsString("paramName1"), "description" -> JsString("Parameter description 1")),
JsObject("name" -> JsString("paramName2"), "description" -> JsString("Parameter description 2"))))
assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
trigger.create(name, annotations = annots)
}
val result = getJSONFromResponse(wsk.trigger.get(name).stdout, true)
val ns = wsk.namespace.whois()
result.fields("name") shouldBe name.toJson
result.fields("namespace") shouldBe ns.toJson
val receivedAnnotations = result.fields("annotations").convertTo[JsArray].elements
val expectedAnnotations = JsArray(
JsObject("key" -> JsString("description"), "value" -> JsString("Trigger description")),
JsObject(
"key" -> JsString("parameters"),
"value" -> JsArray(
JsObject("description" -> JsString("Parameter description 1"), "name" -> JsString("paramName1")),
JsObject("description" -> JsString("Parameter description 2"), "name" -> JsString("paramName2"))))).elements
receivedAnnotations should contain theSameElementsAs expectedAnnotations
}
it should "create a trigger with a name that contains spaces" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "trigger with spaces"
val res = assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
trigger.create(name)
}
res.stdout should include regex (s"ok: created trigger $name")
}
it should "create, and fire a trigger using a parameter file" in withAssetCleaner(wskprops) {
val ruleName = withTimestamp("r1toa1")
val triggerName = withTimestamp("paramFileTrigger")
val actionName = withTimestamp("a1")
val argInput = Some(TestUtils.getTestActionFilename("validInput2.json"))
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
trigger.create(triggerName)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val expectedOutput = JsObject("payload" -> JsString("test"))
val run = wsk.trigger.fire(triggerName, parameterFile = argInput)
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(expectedOutput)
}
}
it should "create a trigger, and get its individual fields" in withAssetCleaner(wskprops) {
val triggerName = "triggerFields"
val ruleName = "triggerFieldsRules"
val actionName = "triggerFieldsAction"
val paramInput = Map("payload" -> "test".toJson)
val successMsg = s"ok: got trigger $triggerName, displaying field"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name, parameters = paramInput)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val expectedParam = JsObject("payload" -> JsString("test"))
val ns = wsk.namespace.whois()
wsk.trigger
.get(triggerName, fieldFilter = Some("namespace"))
.stdout should include regex (s"""(?i)$successMsg namespace\n"$ns"""")
wsk.trigger.get(triggerName, fieldFilter = Some("name")).stdout should include(
s"""$successMsg name\n"$triggerName"""")
wsk.trigger.get(triggerName, fieldFilter = Some("version")).stdout should include(
s"""$successMsg version\n"0.0.1"""")
wsk.trigger.get(triggerName, fieldFilter = Some("publish")).stdout should include(
s"""$successMsg publish\nfalse""")
wsk.trigger.get(triggerName, fieldFilter = Some("annotations")).stdout should include(
s"""$successMsg annotations\n[]""")
wsk.trigger
.get(triggerName, fieldFilter = Some("parameters"))
.stdout should include regex (s"""$successMsg parameters\n\\[\\s+\\{\\s+"key":\\s+"payload",\\s+"value":\\s+"test"\\s+\\}\\s+\\]""")
wsk.trigger.get(triggerName, fieldFilter = Some("limits")).stdout should include(s"""$successMsg limits\n{}""")
wsk.trigger.get(triggerName, fieldFilter = Some("invalid"), expectedExitCode = ERROR_EXIT).stderr should include(
"error: Invalid field filter 'invalid'.")
val expectedRules = JsObject(
ns + "/" + ruleName -> JsObject(
"action" -> JsObject("name" -> JsString(actionName), "path" -> JsString(ns)),
"status" -> JsString("active")))
getJSONFromResponse(wsk.trigger.get(triggerName, fieldFilter = Some("rules")).stdout, isCli = true) shouldBe expectedRules
}
it should "create, and fire a trigger to ensure result is empty" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val ruleName = withTimestamp("r1toa1")
val triggerName = withTimestamp("emptyResultTrigger")
val actionName = withTimestamp("a1")
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
trigger.create(triggerName)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val run = wsk.trigger.fire(triggerName)
withActivation(wsk.activation, run) { activation =>
activation.response.result shouldBe Some(JsObject())
}
}
it should "reject creation of duplicate triggers" in withAssetCleaner(wskprops) {
val name = "dupeTrigger"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.trigger, name) { (trigger, _) =>
trigger.create(name)
}
val stderr = wsk.trigger.create(name, expectedExitCode = CONFLICT).stderr
stderr should include regex (s"""Unable to create trigger '$name': resource already exists \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject delete of trigger that does not exist" in {
val name = "nonexistentTrigger"
val stderr = wsk.trigger.delete(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get trigger '$name'. The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject get of trigger that does not exist" in {
val name = "nonexistentTrigger"
val stderr = wsk.trigger.get(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get trigger '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject firing of a trigger that does not exist" in {
val name = "nonexistentTrigger"
val stderr = wsk.trigger.fire(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to fire trigger '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "create and fire a trigger with a rule whose action has been deleted" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val ruleName1 = withTimestamp("r1toa1")
val ruleName2 = withTimestamp("r2toa2")
val triggerName = withTimestamp("t1tor1r2")
val actionName1 = withTimestamp("a1")
val actionName2 = withTimestamp("a2")
val ns = wsk.namespace.whois()
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
trigger.create(triggerName)
trigger.create(triggerName, update = true)
}
assetHelper.withCleaner(wsk.action, actionName1) { (action, name) =>
action.create(name, defaultAction)
}
wsk.action.create(actionName2, defaultAction) // Delete this after the rule is created
assetHelper.withCleaner(wsk.rule, ruleName1) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName1)
}
assetHelper.withCleaner(wsk.rule, ruleName2) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName2)
}
wsk.action.delete(actionName2)
val run = wsk.trigger.fire(triggerName)
withActivation(wsk.activation, run) { activation =>
activation.duration shouldBe 0L // shouldn't exist but CLI generates it
activation.end shouldBe Instant.EPOCH // shouldn't exist but CLI generates it
activation.logs shouldBe defined
activation.logs.get.size shouldBe 2
val logEntry1 = activation.logs.get(0).parseJson.asJsObject
val logEntry2 = activation.logs.get(1).parseJson.asJsObject
val logs = JsArray(logEntry1, logEntry2)
val ruleActivationId: String = if (logEntry1.getFields("activationId").size == 1) {
logEntry1.fields("activationId").convertTo[String]
} else {
logEntry2.fields("activationId").convertTo[String]
}
val expectedLogs = JsArray(
JsObject(
"statusCode" -> JsNumber(0),
"activationId" -> JsString(ruleActivationId),
"success" -> JsBoolean(true),
"rule" -> JsString(ns + "/" + ruleName1),
"action" -> JsString(ns + "/" + actionName1)),
JsObject(
"statusCode" -> JsNumber(1),
"success" -> JsBoolean(false),
"error" -> JsString("The requested resource does not exist."),
"rule" -> JsString(ns + "/" + ruleName2),
"action" -> JsString(ns + "/" + actionName2)))
logs shouldBe expectedLogs
}
}
behavior of "Wsk Rule CLI"
it should "create rule, get rule, update rule and list rule" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val ruleName = "listRules"
val triggerName = "listRulesTrigger"
val actionName = "listRulesAction"
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
// finally, we perform the update, and expect success this time
wsk.rule.create(ruleName, trigger = triggerName, action = actionName, update = true)
val stdout = wsk.rule.get(ruleName).stdout
stdout should include(ruleName)
stdout should include(triggerName)
stdout should include(actionName)
stdout should include regex (""""version": "0.0.2"""")
wsk.rule.list().stdout should include(ruleName)
}
it should "create rule, get rule, ensure rule is enabled by default" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val ruleName = "enabledRule"
val triggerName = "enabledRuleTrigger"
val actionName = "enabledRuleAction"
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val stdout = wsk.rule.get(ruleName).stdout
stdout should include regex (""""status":\s*"active"""")
}
it should "display a rule summary when --summary flag is used with 'wsk rule get'" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val ruleName = "mySummaryRule"
val triggerName = "summaryRuleTrigger"
val actionName = "summaryRuleAction"
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName, confirmDelete = false) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
// Summary namespace should match one of the allowable namespaces (typically 'guest')
val ns = wsk.namespace.whois()
val stdout = wsk.rule.get(ruleName, summary = true).stdout
stdout should include regex (s"(?i)rule /$ns/$ruleName\\s*\\(status: active\\)")
}
it should "create a rule, and get its individual fields" in withAssetCleaner(wskprops) {
val ruleName = "ruleFields"
val triggerName = "ruleTriggerFields"
val actionName = "ruleActionFields"
val paramInput = Map("payload" -> "test".toJson)
val successMsg = s"ok: got rule $ruleName, displaying field"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val ns = wsk.namespace.whois()
wsk.rule
.get(ruleName, fieldFilter = Some("namespace"))
.stdout should include regex (s"""(?i)$successMsg namespace\n"$ns"""")
wsk.rule.get(ruleName, fieldFilter = Some("name")).stdout should include(s"""$successMsg name\n"$ruleName"""")
wsk.rule.get(ruleName, fieldFilter = Some("version")).stdout should include(s"""$successMsg version\n"0.0.1"\n""")
wsk.rule.get(ruleName, fieldFilter = Some("status")).stdout should include(s"""$successMsg status\n"active"""")
val trigger = wsk.rule.get(ruleName, fieldFilter = Some("trigger")).stdout
trigger should include regex (s"""$successMsg trigger\n""")
trigger should include(triggerName)
trigger should not include (actionName)
val action = wsk.rule.get(ruleName, fieldFilter = Some("action")).stdout
action should include regex (s"""$successMsg action\n""")
action should include(actionName)
action should not include (triggerName)
}
it should "reject creation of duplicate rules" in withAssetCleaner(wskprops) {
val ruleName = "dupeRule"
val triggerName = "triggerName"
val actionName = "actionName"
(wp, assetHelper) =>
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) =>
trigger.create(name)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val stderr =
wsk.rule.create(ruleName, trigger = triggerName, action = actionName, expectedExitCode = CONFLICT).stderr
stderr should include regex (s"""Unable to create rule '$ruleName': resource already exists \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject delete of rule that does not exist" in {
val name = "nonexistentRule"
val stderr = wsk.rule.delete(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to delete rule '$name'. The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject enable of rule that does not exist" in {
val name = "nonexistentRule"
val stderr = wsk.rule.enable(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to enable rule '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject disable of rule that does not exist" in {
val name = "nonexistentRule"
val stderr = wsk.rule.disable(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to disable rule '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject status of rule that does not exist" in {
val name = "nonexistentRule"
val stderr = wsk.rule.state(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get status of rule '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject get of rule that does not exist" in {
val name = "nonexistentRule"
val stderr = wsk.rule.get(name, expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get rule '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
behavior of "Wsk Namespace CLI"
def WskNsCLI_RetListOneNs_test(wsk: Wsk, wp: WskProps): Unit = {
val lines = wsk.namespace.list()(wp).stdout.lines.toSeq
lines should have size 2
lines.head shouldBe "namespaces"
lines(1).trim should not be empty
}
it should "return a list of exactly one namespace" in {
WskNsCLI_RetListOneNs_test(wsk, wskprops)
}
it should "list entities in default namespace" in {
wsk.namespace.get(expectedExitCode = SUCCESS_EXIT)(wskprops).stdout should include("default")
}
behavior of "Wsk Activation CLI"
it should "create a trigger, and fire a trigger to get its individual fields from an activation" in withAssetCleaner(
wskprops) { (wp, assetHelper) =>
val ruleName = withTimestamp("r1toa1")
val triggerName = withTimestamp("activationFields")
val actionName = withTimestamp("a1")
assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
trigger.create(triggerName)
}
assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
action.create(name, defaultAction)
}
assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
rule.create(name, trigger = triggerName, action = actionName)
}
val ns = wsk.namespace.whois()
val run = wsk.trigger.fire(triggerName)
withActivation(wsk.activation, run) { activation =>
var result = wsk.activation.get(Some(activation.activationId))
getJSONFromResponse(result.stdout, true).fields("namespace").convertTo[String] shouldBe ns
getJSONFromResponse(result.stdout, true).fields("name").convertTo[String] shouldBe triggerName
getJSONFromResponse(result.stdout, true).fields("version").convertTo[String] shouldBe "0.0.1"
getJSONFromResponse(result.stdout, true).fields("publish") shouldBe false.toJson
getJSONFromResponse(result.stdout, true).fields("subject").convertTo[String].length should not be (0)
getJSONFromResponse(result.stdout, true).fields("activationId").convertTo[String] shouldBe activation.activationId
getJSONFromResponse(result.stdout, true).fields("start") should not be JsObject()
getJSONFromResponse(result.stdout, true).fields("end") shouldBe 0.toJson
getJSONFromResponse(result.stdout, true).fields("duration") shouldBe 0.toJson
getJSONFromResponse(result.stdout, true).fields("annotations").convertTo[JsArray].elements.length shouldBe 0
}
}
it should "reject get of activation that does not exist" in {
val name = "0" * 32
val stderr = wsk.activation.get(Some(name), expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get activation '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject logs of activation that does not exist" in {
val name = "0" * 32
val stderr = wsk.activation.logs(Some(name), expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get logs for activation '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject result of activation that does not exist" in {
val name = "0" * 32
val stderr = wsk.activation.result(Some(name), expectedExitCode = NOT_FOUND).stderr
stderr should include regex (s"""Unable to get result for activation '$name': The requested resource does not exist. \\(code [0-9a-zA-Z_-]+\\)""")
}
it should "reject activation request when using activation ID with --last Flag" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
val auth: Seq[String] = Seq("--auth", wskprops.authKey)
val lastId = "dummyActivationId"
val tooManyArgsMsg = s"${lastId}. An activation ID is required."
val invalidField = s"Invalid field filter '${lastId}'."
val invalidCmd = Seq(
(Seq("activation", "get", s"$lastId", "publish", "--last"), tooManyArgsMsg),
(Seq("activation", "get", s"$lastId", "--last"), invalidField),
(Seq("activation", "logs", s"$lastId", "--last"), tooManyArgsMsg),
(Seq("activation", "result", s"$lastId", "--last"), tooManyArgsMsg))
invalidCmd foreach {
case (cmd, err) =>
val stderr = wsk.cli(cmd ++ wskprops.overrides ++ auth, expectedExitCode = ERROR_EXIT).stderr
stderr should include(err)
}
}
}