This is an automated email from the ASF dual-hosted git repository.
rabbah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new a8addf6 Extend system test suite (#3950)
a8addf6 is described below
commit a8addf629d56b789d0c1ce503cf6d691e6ecbf72
Author: Martin Gencur <[email protected]>
AuthorDate: Thu Oct 25 20:18:16 2018 +0200
Extend system test suite (#3950)
---
.../src/main/resources/apiv1swagger.json | 6 +++
tests/dat/actions/argsPrint.js | 8 ++++
tests/dat/actions/runexception.js | 6 +++
tests/src/test/scala/common/WskCliOperations.scala | 10 ++++-
tests/src/test/scala/common/WskOperations.scala | 1 +
.../test/scala/common/rest/WskRestOperations.scala | 37 ++++++++++++----
.../test/scala/system/basic/WskActionTests.scala | 37 +++++++++++++++-
.../whisk/core/cli/test/WskEntitlementTests.scala | 34 +++++++++++++++
.../core/cli/test/WskRestBasicUsageTests.scala | 1 +
.../core/controller/test/ActivationsApiTests.scala | 51 ++++++++++++++++++++++
.../core/controller/test/PackagesApiTests.scala | 9 ++++
11 files changed, 189 insertions(+), 11 deletions(-)
diff --git a/core/controller/src/main/resources/apiv1swagger.json
b/core/controller/src/main/resources/apiv1swagger.json
index 989c5fa..8f3c332 100644
--- a/core/controller/src/main/resources/apiv1swagger.json
+++ b/core/controller/src/main/resources/apiv1swagger.json
@@ -1280,6 +1280,9 @@
"401": {
"$ref": "#/responses/UnauthorizedRequest"
},
+ "403": {
+ "$ref": "#/responses/UnauthorizedRequest"
+ },
"500": {
"$ref": "#/responses/ServerError"
}
@@ -1323,6 +1326,9 @@
"401": {
"$ref": "#/responses/UnauthorizedRequest"
},
+ "403": {
+ "$ref": "#/responses/UnauthorizedRequest"
+ },
"404": {
"$ref": "#/responses/ItemNotFound"
},
diff --git a/tests/dat/actions/argsPrint.js b/tests/dat/actions/argsPrint.js
new file mode 100644
index 0000000..6bd35ef
--- /dev/null
+++ b/tests/dat/actions/argsPrint.js
@@ -0,0 +1,8 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+// license agreements; and to You under the Apache License, Version 2.0.
+
+function main(params) {
+ var param1 = params.param1 || '';
+ var param2 = params.param2 || '';
+ return {param1: param1, param2: param2};
+}
diff --git a/tests/dat/actions/runexception.js
b/tests/dat/actions/runexception.js
new file mode 100644
index 0000000..4e15f9e
--- /dev/null
+++ b/tests/dat/actions/runexception.js
@@ -0,0 +1,6 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+// license agreements; and to You under the Apache License, Version 2.0.
+
+function main() {
+ throw "Extraordinary exception"
+}
diff --git a/tests/src/test/scala/common/WskCliOperations.scala
b/tests/src/test/scala/common/WskCliOperations.scala
index 459dc59..259ca9d 100644
--- a/tests/src/test/scala/common/WskCliOperations.scala
+++ b/tests/src/test/scala/common/WskCliOperations.scala
@@ -480,12 +480,14 @@ class CliActivationOperations(val wsk: RunCliCmd) extends
ActivationOperations w
* @param filter (optional) if define, must be a simple entity name
* @param limit (optional) the maximum number of activation to return
* @param since (optional) only the activations since this timestamp are
included
+ * @param skip (optional) the number of activations to skip
* @param expectedExitCode (optional) the expected exit code for the command
* if the code is anything but DONTCARE_EXIT, assert the code is as expected
*/
def list(filter: Option[String] = None,
limit: Option[Int] = None,
since: Option[Instant] = None,
+ skip: Option[Int] = None,
expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps):
RunResult = {
val params = Seq(noun, "list", "--auth", wp.authKey) ++ { filter map {
Seq(_) } getOrElse Seq.empty } ++ {
limit map { l =>
@@ -495,6 +497,10 @@ class CliActivationOperations(val wsk: RunCliCmd) extends
ActivationOperations w
since map { i =>
Seq("--since", i.toEpochMilli.toString)
} getOrElse Seq.empty
+ } ++ {
+ skip map { i =>
+ Seq("--skip", i.toString)
+ } getOrElse Seq.empty
}
wsk.cli(wp.overrides ++ params, expectedExitCode)
}
@@ -606,6 +612,7 @@ class CliActivationOperations(val wsk: RunCliCmd) extends
ActivationOperations w
* @param entity the name of the entity to filter from activation list
* @param limit the maximum number of entities to list (if entity name is
not unique use Some(0))
* @param since (optional) only the activations since this timestamp are
included
+ * @param skip (optional) the number of activations to skip
* @param retries the maximum retries (total timeout is retries + 1 seconds)
* @return activation ids found, caller must check length of sequence
*/
@@ -613,11 +620,12 @@ class CliActivationOperations(val wsk: RunCliCmd) extends
ActivationOperations w
entity: Option[String],
limit: Option[Int] = None,
since: Option[Instant] = None,
+ skip: Option[Int] = Some(0),
retries: Int = 10,
pollPeriod: Duration = 1.second)(implicit wp:
WskProps): Seq[String] = {
Try {
retry({
- val result = ids(list(filter = entity, limit = limit, since = since))
+ val result = ids(list(filter = entity, limit = limit, since = since,
skip = skip))
if (result.length >= N) result else throw PartialResult(result)
}, retries, waitBeforeRetry = Some(pollPeriod))
} match {
diff --git a/tests/src/test/scala/common/WskOperations.scala
b/tests/src/test/scala/common/WskOperations.scala
index 9b012c7..5fb020e 100644
--- a/tests/src/test/scala/common/WskOperations.scala
+++ b/tests/src/test/scala/common/WskOperations.scala
@@ -314,6 +314,7 @@ trait ActivationOperations {
entity: Option[String],
limit: Option[Int] = None,
since: Option[Instant] = None,
+ skip: Option[Int] = None,
retries: Int,
pollPeriod: Duration = 1.second)(implicit wp: WskProps):
Seq[String]
diff --git a/tests/src/test/scala/common/rest/WskRestOperations.scala
b/tests/src/test/scala/common/rest/WskRestOperations.scala
index 94023d7..66f6ef2 100644
--- a/tests/src/test/scala/common/rest/WskRestOperations.scala
+++ b/tests/src/test/scala/common/rest/WskRestOperations.scala
@@ -26,7 +26,6 @@ import org.apache.commons.io.{FileUtils, FilenameUtils}
import org.scalatest.Matchers
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.Span.convertDurationToSpan
-
import scala.collection.immutable.Seq
import scala.concurrent.duration.Duration
import scala.concurrent.duration.DurationInt
@@ -40,7 +39,7 @@ import akka.http.scaladsl.model.StatusCodes.OK
import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.model.HttpMethod
import akka.http.scaladsl.model.HttpResponse
-import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials,
HttpCredentials, OAuth2BearerToken}
+import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials,
OAuth2BearerToken}
import akka.http.scaladsl.model.HttpEntity
import akka.http.scaladsl.model.ContentTypes
import akka.http.scaladsl.Http
@@ -76,6 +75,7 @@ import akka.actor.ActorSystem
import akka.util.ByteString
import pureconfig.loadConfigOrThrow
import whisk.common.Https.HttpsConfig
+import whisk.common.AkkaLogging
class AcceptAllHostNameVerifier extends HostnameVerifier {
override def verify(s: String, sslSession: SSLSession): Boolean = true
@@ -644,10 +644,12 @@ class RestActivationOperations(implicit val actorSystem:
ActorSystem)
def listActivation(filter: Option[String] = None,
limit: Option[Int] = None,
since: Option[Instant] = None,
+ skip: Option[Int] = None,
docs: Boolean = true,
expectedExitCode: Int = SUCCESS_EXIT)(implicit wp:
WskProps): RestResult = {
val entityPath = Path(s"${basePath}/namespaces/${wp.namespace}/$noun")
- val paramMap = Map("skip" -> "0", "docs" -> docs.toString) ++
+ val paramMap = Map("docs" -> docs.toString) ++
+ skip.map(s => Map("skip" -> s.toString)).getOrElse(Map.empty) ++
limit.map(l => Map("limit" -> l.toString)).getOrElse(Map.empty) ++
filter.map(f => Map("name" -> f.toString)).getOrElse(Map.empty) ++
since.map(s => Map("since" ->
s.toEpochMilli.toString)).getOrElse(Map.empty)
@@ -706,6 +708,7 @@ class RestActivationOperations(implicit val actorSystem:
ActorSystem)
* @param entity the name of the entity to filter from activation list
* @param limit the maximum number of entities to list (if entity name is
not unique use Some(0))
* @param since (optional) only the activations since this timestamp are
included
+ * @param skip (optional) the number of activations to skip
* @param retries the maximum retries (total timeout is retries + 1 seconds)
* @return activation ids found, caller must check length of sequence
*/
@@ -713,11 +716,13 @@ class RestActivationOperations(implicit val actorSystem:
ActorSystem)
entity: Option[String],
limit: Option[Int] = Some(30),
since: Option[Instant] = None,
+ skip: Option[Int] = Some(0),
retries: Int = 10,
pollPeriod: Duration = 1.second)(implicit wp:
WskProps): Seq[String] = {
Try {
retry({
- val result = idsActivation(listActivation(filter = entity, limit =
limit, since = since, docs = false))
+ val result =
+ idsActivation(listActivation(filter = entity, limit = limit, since =
since, skip = skip, docs = false))
if (result.length >= N) result else throw PartialResult(result)
}, retries, waitBeforeRetry = Some(pollPeriod))
} match {
@@ -732,13 +737,23 @@ class RestActivationOperations(implicit val actorSystem:
ActorSystem)
fieldFilter: Option[String] = None,
last: Option[Boolean] = None,
summary: Option[Boolean] = None)(implicit wp: WskProps):
RestResult = {
- val rr = activationId match {
+ val actId = activationId match {
+ case Some(id) => activationId
+ case None =>
+ last match {
+ case Some(true) => {
+ val activations = pollFor(N = 1, entity = None, limit = Some(1))
+ require(activations.size <= 1)
+ if (activations.isEmpty) None else Some(activations.head)
+ }
+ case _ => None
+ }
+ }
+ val rr = actId match {
case Some(id) =>
val resp = requestEntity(GET, getNamePath(wp.namespace, noun, id))
new RestResult(resp.status, getRespData(resp))
-
- case None =>
- new RestResult(NotFound)
+ case None => new RestResult(NotFound)
}
validateStatusCode(expectedExitCode, rr.statusCode.intValue)
rr
@@ -1141,6 +1156,7 @@ trait RunRestCmd extends Matchers with ScalaFutures with
SwaggerValidator {
val maxOpenRequest = 1024
val basePath = Path("/api/v1")
val systemNamespace = "whisk.system"
+ val logger = new AkkaLogging(actorSystem.log)
implicit val config = PatienceConfig(100 seconds, 15 milliseconds)
implicit val actorSystem: ActorSystem
@@ -1192,6 +1208,9 @@ trait RunRestCmd extends Matchers with ScalaFutures with
SwaggerValidator {
body.map(b => HttpEntity.Strict(ContentTypes.`application/json`,
ByteString(b))).getOrElse(HttpEntity.Empty))
val response = Http().singleRequest(request, connectionContext).flatMap {
_.toStrict(toStrictTimeout) }.futureValue
+ logger.debug(this, s"Request: $request")
+ logger.debug(this, s"Response: $response")
+
val validationErrors = validateRequestAndResponse(request, response)
if (validationErrors.nonEmpty) {
fail(
@@ -1201,7 +1220,7 @@ trait RunRestCmd extends Matchers with ScalaFutures with
SwaggerValidator {
response
}
- private def getHttpCredentials(wp: WskProps): HttpCredentials = {
+ private def getHttpCredentials(wp: WskProps) = {
if (wp.authKey.contains(":")) {
val authKey = wp.authKey.split(":")
new BasicHttpCredentials(authKey(0), authKey(1))
diff --git a/tests/src/test/scala/system/basic/WskActionTests.scala
b/tests/src/test/scala/system/basic/WskActionTests.scala
index 6db4b22..7830da7 100644
--- a/tests/src/test/scala/system/basic/WskActionTests.scala
+++ b/tests/src/test/scala/system/basic/WskActionTests.scala
@@ -32,7 +32,7 @@ import spray.json.DefaultJsonProtocol._
class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers
with WskActorSystem {
implicit val wskprops = WskProps()
- val wsk: WskOperations = new WskRestOperations
+ val wsk = new WskRestOperations
val testString = "this is a test"
val testResult = JsObject("count" -> testString.split(" ").length.toJson)
@@ -75,6 +75,21 @@ class WskActionTests extends TestHelpers with WskTestHelpers
with JsHelpers with
}
}
+ it should "invoke an action that throws an uncaught exception and returns
correct status code" in withAssetCleaner(
+ wskprops) { (wp, assetHelper) =>
+ val name = "throwExceptionAction"
+ assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+ action.create(name,
Some(TestUtils.getTestActionFilename("runexception.js")))
+ }
+
+ withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+ val response = activation.response
+ activation.response.status shouldBe "action developer error"
+ activation.response.result shouldBe Some(
+ JsObject("error" -> "An error has occurred: Extraordinary
exception".toJson))
+ }
+ }
+
it should "pass parameters bound on creation-time to the action" in
withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "printParams"
val params = Map("param1" -> "test1", "param2" -> "test2")
@@ -214,6 +229,26 @@ class WskActionTests extends TestHelpers with
WskTestHelpers with JsHelpers with
}
}
+ it should "update an action with different language and check preserving
params" in withAssetCleaner(wskprops) {
+ (wp, assetHelper) =>
+ val name = "updatedAction"
+
+ assetHelper.withCleaner(wsk.action, name, false) { (action, _) =>
+ wsk.action.create(
+ name,
+ Some(TestUtils.getTestActionFilename("hello.js")),
+ parameters = Map("name" -> testString.toJson)) //unused in the first
function
+ }
+
+ wsk.action.create(name,
Some(TestUtils.getTestActionFilename("hello.py")), update = true)
+
+ val run = wsk.action.invoke(name)
+ withActivation(wsk.activation, run) { activation =>
+ activation.response.status shouldBe "success"
+ activation.logs.get.mkString(" ") should include(s"Hello $testString")
+ }
+ }
+
it should "fail to invoke an action with an empty file" in
withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "empty"
assetHelper.withCleaner(wsk.action, name) { (action, _) =>
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
b/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
index 0e689c8..64a7793 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
@@ -160,6 +160,40 @@ abstract class WskEntitlementTests extends TestHelpers
with WskTestHelpers with
}
}
+ it should "list shared packages when package is turned into public" in
withAssetCleaner(guestWskProps) {
+ (wp, assetHelper) =>
+ assetHelper.withCleaner(wsk.pkg, samplePackage) { (pkg, _) =>
+ pkg.create(samplePackage)(wp)
+ }
+
+ retry {
+ val packageList =
wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps)
+ verifyPackageNotSharedList(packageList, guestNamespace, samplePackage)
+ }
+
+ wsk.pkg.create(samplePackage, update = true, shared = Some(true))(wp)
+
+ retry {
+ val packageList =
wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps)
+ verifyPackageSharedList(packageList, guestNamespace, samplePackage)
+ }
+ }
+
+ //TODO: convert to API-level test under whisk.core.controller once
issues/3959 is resolved
+ it should "reject getting package from invalid namespace" in
withAssetCleaner(guestWskProps) { (wp, assetHelper) =>
+ val invalidNamespace = "whisk.systsdf"
+ wsk.pkg.get(s"/${invalidNamespace}/utils", expectedExitCode =
forbiddenCode)(wp).stderr should include(
+ "not authorized")
+ }
+
+ //TODO: convert to API-level test under whisk.core.controller once
issues/3959 is resolved
+ it should "reject getting invalid package from valid namespace" in
withAssetCleaner(guestWskProps) {
+ (wp, assetHelper) =>
+ val invalidPackage = "utilssss"
+ wsk.pkg.get(s"/whisk.system/${invalidPackage}", expectedExitCode =
forbiddenCode)(wp).stderr should include(
+ "not authorized")
+ }
+
def verifyPackageSharedList(packageList: RunResult, namespace: String,
packageName: String): Unit = {
val fullyQualifiedPackageName = s"/$namespace/$packageName"
withClue(s"Packagelist is: ${packageList.stdout}; Packagename is:
$fullyQualifiedPackageName")(
diff --git
a/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
b/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
index 7fd950a..097ed25 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
@@ -342,6 +342,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
}
val args = Map("hello" -> "Robert".toJson)
val run = wsk.action.invoke(name, args, blocking = true, result = true)
+ //--result takes precedence over --blocking
run.stdout.parseJson shouldBe args.toJson
}
diff --git
a/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
b/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
index 6810ef2..7d5bf85 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ActivationsApiTests.scala
@@ -468,6 +468,57 @@ class ActivationsApiTests extends ControllerTestCommon
with WhiskActivationsApi
}
}
+ it should "skip activations and return correct ones" in {
+ implicit val tid = transid()
+ val activations: Seq[WhiskActivation] = (1 to 3).map { i =>
+ //make sure the time is different for each activation
+ val time = Instant.now.plusMillis(i)
+ WhiskActivation(namespace, aname(), creds.subject,
ActivationId.generate(), start = time, end = time)
+ }.toList
+
+ try {
+ activations.foreach(storeActivation(_, context))
+ waitOnListActivationsInNamespace(namespace, activations.size, context)
+
+ Get(s"$collectionPath?skip=1") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val resultActivationIds =
responseAs[List[JsObject]].map(_.fields("name"))
+ val expectedActivationIds =
activations.map(_.toJson.fields("name")).reverse.drop(1)
+ resultActivationIds should be(expectedActivationIds)
+ }
+ } finally {
+ activations.foreach(a =>
deleteActivation(ActivationId(a.docid.asString), context))
+ waitOnListActivationsInNamespace(namespace, 0, context)
+ }
+ }
+
+ it should "return last activation" in {
+ implicit val tid = transid()
+ val activations = (1 to 3).map { i =>
+ //make sure the time is different for each activation
+ val time = Instant.now.plusMillis(i)
+ WhiskActivation(namespace, aname(), creds.subject,
ActivationId.generate(), start = time, end = time)
+ }.toList
+
+ try {
+ activations.foreach(storeActivation(_, context))
+ waitOnListActivationsInNamespace(namespace, activations.size, context)
+
+ Get(s"$collectionPath?limit=1") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val activationsJson = activations.map(_.toJson)
+ withClue(s"Original activations: ${activationsJson}") {
+ val respNames = responseAs[List[JsObject]].map(_.fields("name"))
+ val expectNames = activationsJson.map(_.fields("name")).drop(2)
+ respNames should be(expectNames)
+ }
+ }
+ } finally {
+ activations.foreach(a =>
deleteActivation(ActivationId(a.docid.asString), context))
+ waitOnListActivationsInNamespace(namespace, 0, context)
+ }
+ }
+
//// GET /activations/id
it should "get activation by id" in {
implicit val tid = transid()
diff --git
a/tests/src/test/scala/whisk/core/controller/test/PackagesApiTests.scala
b/tests/src/test/scala/whisk/core/controller/test/PackagesApiTests.scala
index 88ad062..1be2065 100644
--- a/tests/src/test/scala/whisk/core/controller/test/PackagesApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/PackagesApiTests.scala
@@ -797,6 +797,15 @@ class PackagesApiTests extends ControllerTestCommon with
WhiskPackagesApi {
}
}
+ it should "return empty list for invalid namespace" in {
+ implicit val tid = transid()
+ val path = s"/whisk.systsdf/${collection.path}"
+ Get(path) ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ responseAs[List[JsObject]] should be(List.empty)
+ }
+ }
+
it should "reject bind to non-package" in {
implicit val tid = transid()
val action = WhiskAction(namespace, aname(), jsDefault("??"))