diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala index 887adc38788..1b9fa4eb16c 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala @@ -19,6 +19,7 @@ package org.apache.openwhisk.core.entity import java.io.{ByteArrayInputStream, ByteArrayOutputStream} import java.nio.charset.StandardCharsets.UTF_8 +import java.time.Instant import java.util.Base64 import akka.http.scaladsl.model.ContentTypes @@ -122,7 +123,8 @@ abstract class WhiskActionLikeMetaData(override val name: EntityName) extends Wh * @param limits the limits to impose on the action * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the action + * @param annotations the set of annotations to attribute to the action + * @param updated the timestamp when the action is updated * @throws IllegalArgumentException if any argument is undefined */ @throws[IllegalArgumentException] @@ -133,7 +135,8 @@ case class WhiskAction(namespace: EntityPath, limits: ActionLimits = ActionLimits(), version: SemVer = SemVer(), publish: Boolean = false, - annotations: Parameters = Parameters()) + annotations: Parameters = Parameters(), + override val updated: Instant = WhiskEntity.currentMillis()) extends WhiskActionLike(name) { require(exec != null, "exec undefined") @@ -200,6 +203,7 @@ case class WhiskActionMetaData(namespace: EntityPath, version: SemVer = SemVer(), publish: Boolean = false, annotations: Parameters = Parameters(), + override val updated: Instant = WhiskEntity.currentMillis(), binding: Option[EntityPath] = None) extends WhiskActionLikeMetaData(name) { @@ -264,7 +268,7 @@ case class WhiskActionMetaData(namespace: EntityPath, * @param limits the limits to impose on the action * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the action + * @param annotations the set of annotations to attribute to the action * @param binding the path of the package binding if any * @throws IllegalArgumentException if any argument is undefined */ @@ -330,7 +334,7 @@ case class ExecutableWhiskActionMetaData(namespace: EntityPath, require(limits != null, "limits undefined") def toWhiskAction = - WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations) + WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations, updated) .revision[WhiskActionMetaData](rev) /** @@ -342,11 +346,13 @@ case class ExecutableWhiskActionMetaData(namespace: EntityPath, } object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[WhiskAction] with DefaultJsonProtocol { + import WhiskActivation.instantSerdes val execFieldName = "exec" val requireWhiskAuthHeader = "x-require-whisk-auth" override val collectionName = "actions" + override val cacheEnabled = true override implicit val serdes = jsonFormat( WhiskAction.apply, @@ -357,9 +363,8 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[ "limits", "version", "publish", - "annotations") - - override val cacheEnabled = true + "annotations", + "updated") // overriden to store attached code override def put[A >: WhiskAction](db: ArtifactStore[A], doc: WhiskAction, old: Option[WhiskAction])( @@ -547,7 +552,10 @@ object WhiskActionMetaData with WhiskEntityQueries[WhiskActionMetaData] with DefaultJsonProtocol { + import WhiskActivation.instantSerdes + override val collectionName = "actions" + override val cacheEnabled = true override implicit val serdes = jsonFormat( WhiskActionMetaData.apply, @@ -559,10 +567,9 @@ object WhiskActionMetaData "version", "publish", "annotations", + "updated", "binding") - override val cacheEnabled = true - /** * Resolves an action name if it is contained in a package. * Look up the package to determine if it is a binding or the actual package. diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala index baa64621cc0..0fd81d38ef0 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala @@ -19,10 +19,9 @@ package org.apache.openwhisk.core.entity import java.time.Clock import java.time.Instant +import java.time.temporal.ChronoUnit -import scala.Stream import scala.util.Try - import spray.json._ import org.apache.openwhisk.core.database.DocumentUnreadable import org.apache.openwhisk.core.database.DocumentTypeMismatchException @@ -37,7 +36,7 @@ import org.apache.openwhisk.http.Messages * @param namespace the namespace for the entity as an abstract field * @param version the semantic version as an abstract field * @param publish true to share the entity and false to keep it private as an abstract field - * @param annotation the set of annotations to attribute to the entity + * @param annotations the set of annotations to attribute to the entity * * @throws IllegalArgumentException if any argument is undefined */ @@ -49,7 +48,7 @@ abstract class WhiskEntity protected[entity] (en: EntityName, val entityType: St val version: SemVer val publish: Boolean val annotations: Parameters - val updated = Instant.now(Clock.systemUTC()) + val updated = WhiskEntity.currentMillis() /** * The name of the entity qualified with its namespace and version for @@ -112,6 +111,15 @@ object WhiskEntity { def qualifiedName(namespace: EntityPath, activationId: ActivationId) = { s"$namespace${EntityPath.PATHSEP}$activationId" } + + /** + * Get Instant object with a millisecond precision + * timestamp of whisk entity is stored in milliseconds in the db + */ + def currentMillis() = { + Instant.now(Clock.systemUTC()).truncatedTo(ChronoUnit.MILLIS) + } + } object WhiskDocumentReader extends DocumentReader { diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala index c3c5050d100..c606867852f 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala @@ -17,11 +17,12 @@ package org.apache.openwhisk.core.entity +import java.time.Instant + import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.language.postfixOps import scala.util.Try - import spray.json.DefaultJsonProtocol import spray.json.DefaultJsonProtocol._ import spray.json._ @@ -60,7 +61,8 @@ case class WhiskPackagePut(binding: Option[Binding] = None, * @param parameters the set of parameters to bind to the action environment * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the package + * @param annotations the set of annotations to attribute to the package + * @param updated the timestamp when the package is updated * @throws IllegalArgumentException if any argument is undefined */ @throws[IllegalArgumentException] @@ -70,7 +72,8 @@ case class WhiskPackage(namespace: EntityPath, parameters: Parameters = Parameters(), version: SemVer = SemVer(), publish: Boolean = false, - annotations: Parameters = Parameters()) + annotations: Parameters = Parameters(), + override val updated: Instant = WhiskEntity.currentMillis()) extends WhiskEntity(name, "package") { require(binding != null || (binding map { _ != null } getOrElse true), "binding undefined") @@ -159,6 +162,8 @@ object WhiskPackage with WhiskEntityQueries[WhiskPackage] with DefaultJsonProtocol { + import WhiskActivation.instantSerdes + val bindingFieldName = "binding" override val collectionName = "packages" @@ -197,7 +202,7 @@ object WhiskPackage override def write(b: Option[Binding]) = Binding.optionalBindingSerializer.write(b) override def read(js: JsValue) = Binding.optionalBindingDeserializer.read(js) } - jsonFormat7(WhiskPackage.apply) + jsonFormat8(WhiskPackage.apply) } override val cacheEnabled = true diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala index 6a7e929bcf5..259a6123d4f 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala @@ -17,10 +17,11 @@ package org.apache.openwhisk.core.entity +import java.time.Instant + import scala.util.Failure import scala.util.Success import scala.util.Try - import spray.json.DefaultJsonProtocol import spray.json.DeserializationException import spray.json.JsObject @@ -65,7 +66,8 @@ case class WhiskRulePut(trigger: Option[FullyQualifiedEntityName] = None, * @param action the action name to invoke invoke when trigger is fired * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the rule + * @param annotations the set of annotations to attribute to the rule + * @param updated the timestamp when the rule is updated * @throws IllegalArgumentException if any argument is undefined */ @throws[IllegalArgumentException] @@ -75,10 +77,12 @@ case class WhiskRule(namespace: EntityPath, action: FullyQualifiedEntityName, version: SemVer = SemVer(), publish: Boolean = false, - annotations: Parameters = Parameters()) + annotations: Parameters = Parameters(), + override val updated: Instant = WhiskEntity.currentMillis()) extends WhiskEntity(name, "rule") { - def withStatus(s: Status) = WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations) + def withStatus(s: Status) = + WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations, updated) def toJson = WhiskRule.serdes.write(this).asJsObject } @@ -95,7 +99,7 @@ case class WhiskRule(namespace: EntityPath, * @param action the action name to invoke invoke when trigger is fired * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the rule + * @param annotations the set of annotations to attribute to the rule */ case class WhiskRuleResponse(namespace: EntityPath, name: EntityName, @@ -104,7 +108,8 @@ case class WhiskRuleResponse(namespace: EntityPath, action: FullyQualifiedEntityName, version: SemVer = SemVer(), publish: Boolean = false, - annotations: Parameters = Parameters()) { + annotations: Parameters = Parameters(), + updated: Instant) { def toWhiskRule = WhiskRule(namespace, name, trigger, action, version, publish, annotations) } @@ -195,11 +200,12 @@ protected[core] object Status extends ArgNormalizer[Status] { } object WhiskRule extends DocumentFactory[WhiskRule] with WhiskEntityQueries[WhiskRule] with DefaultJsonProtocol { + import WhiskActivation.instantSerdes override val collectionName = "rules" private implicit val fqnSerdes = FullyQualifiedEntityName.serdes - private val caseClassSerdes = jsonFormat7(WhiskRule.apply) + private val caseClassSerdes = jsonFormat8(WhiskRule.apply) override implicit val serdes = new RootJsonFormat[WhiskRule] { def write(r: WhiskRule) = caseClassSerdes.write(r) @@ -233,8 +239,9 @@ object WhiskRule extends DocumentFactory[WhiskRule] with WhiskEntityQueries[Whis } object WhiskRuleResponse extends DefaultJsonProtocol { + import WhiskActivation.instantSerdes private implicit val fqnSerdes = FullyQualifiedEntityName.serdes - implicit val serdes = jsonFormat8(WhiskRuleResponse.apply) + implicit val serdes = jsonFormat9(WhiskRuleResponse.apply) } object WhiskRulePut extends DefaultJsonProtocol { diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala index d6668a9cb92..0470980812b 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala @@ -17,6 +17,8 @@ package org.apache.openwhisk.core.entity +import java.time.Instant + import spray.json.DefaultJsonProtocol import org.apache.openwhisk.core.database.DocumentFactory import spray.json._ @@ -54,8 +56,9 @@ case class ReducedRule(action: FullyQualifiedEntityName, status: Status) * @param limits the limits to impose on the trigger * @param version the semantic version * @param publish true to share the action or false otherwise - * @param annotation the set of annotations to attribute to the trigger + * @param annotations the set of annotations to attribute to the trigger * @param rules the map of the rules that are associated with this trigger. Key is the rulename and value is the ReducedRule + * @param updated the timestamp when the trigger is updated * @throws IllegalArgumentException if any argument is undefined */ @throws[IllegalArgumentException] @@ -66,7 +69,8 @@ case class WhiskTrigger(namespace: EntityPath, version: SemVer = SemVer(), publish: Boolean = false, annotations: Parameters = Parameters(), - rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None) + rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None, + override val updated: Instant = WhiskEntity.currentMillis()) extends WhiskEntity(name, "trigger") { require(limits != null, "limits undefined") @@ -108,11 +112,12 @@ object WhiskTrigger extends DocumentFactory[WhiskTrigger] with WhiskEntityQueries[WhiskTrigger] with DefaultJsonProtocol { + import WhiskActivation.instantSerdes override val collectionName = "triggers" private implicit val fqnSerdesAsDocId = FullyQualifiedEntityName.serdesAsDocId - override implicit val serdes = jsonFormat8(WhiskTrigger.apply) + override implicit val serdes = jsonFormat9(WhiskTrigger.apply) override val cacheEnabled = true } diff --git a/core/controller/src/main/resources/apiv1swagger.json b/core/controller/src/main/resources/apiv1swagger.json index 4a3aca7cf98..b9c1060a962 100644 --- a/core/controller/src/main/resources/apiv1swagger.json +++ b/core/controller/src/main/resources/apiv1swagger.json @@ -1998,6 +1998,10 @@ "deactivating" ] }, + "updated": { + "type": "integer", + "description": "Time when the rule was updated" + }, "trigger": { "$ref": "#/definitions/PathName" }, diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala index 95a1425b04a..8758c0d9837 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala @@ -194,6 +194,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { implicit val tid = transid() val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b")) put(entityStore, action) + Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] @@ -201,20 +202,42 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { } } - it should "get action by name in explicit namespace" in { + it should "get action with updated field" in { implicit val tid = transid() + val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b")) put(entityStore, action) + + // `updated` field should be compared with a document in DB + val a = get(entityStore, action.docid, WhiskAction) + + Get(s"/$namespace/${collection.path}/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val responseJson = responseAs[JsObject] + responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli) + } + Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) - val response = responseAs[WhiskAction] - response should be(action) + val responseJson = responseAs[JsObject] + responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli) } + } - // it should "reject get action by name in explicit namespace not owned by subject" in - val auser = WhiskAuthHelpers.newIdentity() - Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(auser)) ~> check { - status should be(Forbidden) + it should "ignore updated field when updating action" in { + implicit val tid = transid() + + val action = WhiskAction(namespace, aname(), jsDefault("")) + val dummyUpdated = WhiskEntity.currentMillis().toEpochMilli + + val content = JsObject( + "exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson), + "updated" -> dummyUpdated.toJson) + + Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskAction] + response.updated.toEpochMilli should be > dummyUpdated } } @@ -324,7 +347,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be(expectedWhiskAction) + checkWhiskEntityResponse(response, expectedWhiskAction) } Get(s"$collectionPath/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check { @@ -332,21 +355,21 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { val responseJson = responseAs[JsObject] responseJson.fields("exec").asJsObject.fields should not(contain key "code") val response = responseAs[WhiskActionMetaData] - response should be(expectedWhiskActionMetaData) + checkWhiskEntityResponse(response, expectedWhiskActionMetaData) } Seq(s"$collectionPath/${action.name}", s"$collectionPath/${action.name}?code=true").foreach { path => Get(path) ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be(expectedWhiskAction) + checkWhiskEntityResponse(response, expectedWhiskAction) } } Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be(expectedWhiskAction) + checkWhiskEntityResponse(response, expectedWhiskAction) } } } @@ -545,7 +568,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -566,7 +590,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -589,7 +614,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -617,7 +643,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -635,7 +662,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -706,7 +734,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -746,7 +775,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -760,6 +790,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { } it should "put and then get an action from cache" in { + implicit val tid = transid() val javaAction = WhiskAction(namespace, aname(), javaDefault("ZHViZWU=", Some("hello")), annotations = Parameters("exec", "java")) val nodeAction = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b")) @@ -781,7 +812,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -800,7 +832,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -818,7 +851,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be { + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -827,8 +861,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { action.limits, action.version.upPatch, action.publish, - action.annotations ++ systemAnnotations(kind)) - } + action.annotations ++ systemAnnotations(kind))) } stream.toString should include(s"entity exists, will try to update '$action'") stream.toString should include(s"invalidating ${CacheKey(action)}") @@ -839,7 +872,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -893,7 +927,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -913,7 +948,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -932,7 +968,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -976,7 +1013,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/$name", content) ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -996,7 +1034,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1016,7 +1055,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1065,7 +1105,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1124,7 +1165,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be(expectedAction) + checkWhiskEntityResponse(response, expectedAction) } } @@ -1172,7 +1213,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/$name?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1190,7 +1232,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1237,7 +1280,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Put(s"$collectionPath/${actionOldSchema.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check { val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( actionOldSchema.namespace, actionOldSchema.name, @@ -1261,7 +1305,8 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { Delete(s"$collectionPath/${actionOldSchema.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskAction] - response should be( + checkWhiskEntityResponse( + response, WhiskAction( actionOldSchema.namespace, actionOldSchema.name, @@ -1301,7 +1346,10 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be { + + response.updated should not be action.updated + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, @@ -1313,8 +1361,7 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { content.limits.get.logs.get, content.limits.get.concurrency.get), version = action.version.upPatch, - annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false)) - } + annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false))) } } @@ -1327,15 +1374,15 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { deleteAction(action.docid) status should be(OK) val response = responseAs[WhiskAction] - response should be { + checkWhiskEntityResponse( + response, WhiskAction( action.namespace, action.name, action.exec, content.parameters.get, version = action.version.upPatch, - annotations = action.annotations ++ systemAnnotations(NODEJS10, false)) - } + annotations = action.annotations ++ systemAnnotations(NODEJS10, false))) } } diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala index fca9fafd13c..9817bef6052 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala @@ -85,6 +85,18 @@ protected trait ControllerTestCommon } } + def checkWhiskEntityResponse(response: WhiskEntity, expected: WhiskEntity): Unit = { + // Used to ignore `updated` field because timestamp is not known before inserting into the DB + // If you use this method, test case that checks timestamp must be added + val r = response match { + case whiskAction: WhiskAction => whiskAction.copy(updated = expected.updated) + case whiskActionMetaData: WhiskActionMetaData => whiskActionMetaData.copy(updated = expected.updated) + case whiskTrigger: WhiskTrigger => whiskTrigger.copy(updated = expected.updated) + case whiskPackage: WhiskPackage => whiskPackage.copy(updated = expected.updated) + } + r should be(expected) + } + def systemAnnotations(kind: String, create: Boolean = true): Parameters = { val base = if (create && FeatureFlags.requireApiKeyAnnotation) { Parameters(Annotations.ProvideApiKeyAnnotationName, JsFalse) diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala index d1033b00828..a645e87c3ee 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala @@ -162,7 +162,8 @@ class PackageActionsApiTests extends ControllerTestCommon with WhiskActionsApi { action.limits, action.version, action.publish, - action.annotations ++ systemAnnotations(NODEJS10))) + action.annotations ++ systemAnnotations(NODEJS10), + updated = response.updated)) } } diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala index 5d1ecca243b..5ea915d8a41 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala @@ -297,6 +297,21 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { } } + it should "get package with updated field" in { + implicit val tid = transid() + val provider = WhiskPackage(namespace, aname(), None) + put(entityStore, provider) + + // `updated` field should be compared with a document in DB + val pkg = get(entityStore, provider.docid, WhiskPackage) + + Get(s"$collectionPath/${provider.name}") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskPackageWithActions] + response should be(provider copy (updated = pkg.updated) withActions ()) + } + } + it should "get package reference for private package in same namespace" in { implicit val tid = transid() val provider = WhiskPackage(namespace, aname(), None, Parameters("a", "A") ++ Parameters("b", "B")) @@ -422,7 +437,7 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { deletePackage(provider.docid) status should be(OK) val response = responseAs[WhiskPackage] - response should be(provider) + checkWhiskEntityResponse(response, provider) } } @@ -492,7 +507,7 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { deletePackage(reference.docid) status should be(OK) val response = responseAs[WhiskPackage] - response should be(reference) + checkWhiskEntityResponse(response, reference) } } @@ -522,13 +537,13 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { deletePackage(reference.docid) status should be(OK) val response = responseAs[WhiskPackage] - response should be { + checkWhiskEntityResponse( + response, WhiskPackage( reference.namespace, reference.name, provider.bind, - annotations = bindingAnnotation(provider.bind.get)) - } + annotations = bindingAnnotation(provider.bind.get))) } } @@ -632,7 +647,8 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { Put(s"$collectionPath/${provider.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check { deletePackage(provider.docid) val response = responseAs[WhiskPackage] - response should be( + checkWhiskEntityResponse( + response, WhiskPackage(namespace, provider.name, None, version = provider.version.upPatch, publish = true)) } } @@ -657,15 +673,15 @@ class PackagesApiTests extends ControllerTestCommon with WhiskPackagesApi { deletePackage(reference.docid) status should be(OK) val response = responseAs[WhiskPackage] - response should be { + checkWhiskEntityResponse( + response, WhiskPackage( reference.namespace, reference.name, reference.binding, version = reference.version.upPatch, publish = true, - annotations = reference.annotations ++ Parameters("a", "b")) - } + annotations = reference.annotations ++ Parameters("a", "b"))) } } diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala index d497593d29f..9aee5dab835 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala @@ -17,6 +17,8 @@ package org.apache.openwhisk.core.controller.test +import java.time.Instant + import scala.language.postfixOps import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -27,7 +29,7 @@ import spray.json.DefaultJsonProtocol._ import spray.json._ import org.apache.openwhisk.core.controller.WhiskRulesApi import org.apache.openwhisk.core.entitlement.Collection -import org.apache.openwhisk.core.entity._ +import org.apache.openwhisk.core.entity.{WhiskRuleResponse, _} import org.apache.openwhisk.core.entity.test.OldWhiskTrigger import org.apache.openwhisk.http.ErrorResponse @@ -61,6 +63,11 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { val activeStatus = s"""{"status":"${Status.ACTIVE}"}""".parseJson.asJsObject val inactiveStatus = s"""{"status":"${Status.INACTIVE}"}""".parseJson.asJsObject val parametersLimit = Parameters.sizeLimit + val dummyInstant = Instant.now() + + def checkResponse(response: WhiskRuleResponse, expected: WhiskRuleResponse) = + // ignore `updated` field because another test covers it + response should be(expected copy (updated = response.updated)) //// GET /rules it should "list rules by default/explicit namespace" in { @@ -164,14 +171,14 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } // it should "get trigger by name in explicit namespace owned by subject" in Get(s"/$namespace/${collection.path}/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } // it should "reject get trigger by name in explicit namespace not owned by subject" in @@ -207,7 +214,32 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) + } + } + + it should "get rule with updated field" in { + implicit val tid = transid() + + val rule = WhiskRule( + namespace, + EntityName("get_active_rule"), + afullname(namespace, "get_active_rule trigger"), + afullname(namespace, "an action")) + val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name, rules = Some { + Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE)) + }) + + put(entityStore, trigger) + put(entityStore, rule) + + // `updated` field should be compared with a document in DB + val r = get(entityStore, rule.docid, WhiskRule) + + Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskRuleResponse] + response should be(rule.withStatus(Status.ACTIVE) copy (updated = r.updated)) } } @@ -227,7 +259,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } } @@ -264,7 +296,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } } @@ -292,7 +324,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe None val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } } @@ -310,7 +342,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { Delete(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } } @@ -329,7 +361,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.withStatus(Status.INACTIVE)) } } @@ -356,7 +388,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) } } @@ -385,7 +417,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) } } @@ -436,7 +468,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) } } @@ -468,7 +500,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) } } @@ -592,14 +624,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.INACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -623,14 +657,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.INACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -654,14 +690,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.INACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -685,14 +723,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe a[Some[_]] val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.INACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -713,14 +753,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.INACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -795,14 +837,16 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) val response = responseAs[WhiskRuleResponse] - response should be( + checkResponse( + response, WhiskRuleResponse( namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), - version = SemVer().upPatch)) + version = SemVer().upPatch, + updated = dummyInstant)) } } @@ -948,7 +992,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check { status should be(OK) val response = responseAs[WhiskRuleResponse] - response should be(rule.toWhiskRule.withStatus(Status.INACTIVE)) + checkResponse(response, rule.toWhiskRule.withStatus(Status.INACTIVE)) } } @@ -972,7 +1016,7 @@ class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { status should be(OK) t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) val response = responseAs[WhiskRuleResponse] - response should be(rule.withStatus(Status.ACTIVE)) + checkResponse(response, rule.withStatus(Status.ACTIVE)) } } diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala index 91b22a279c8..d87751b4daa 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala @@ -67,6 +67,7 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { def aname() = MakeName.next("triggers_tests") def afullname(namespace: EntityPath, name: String) = FullyQualifiedEntityName(namespace, EntityName(name)) val parametersLimit = Parameters.sizeLimit + val dummyInstant = Instant.now() //// GET /triggers it should "list triggers by default/explicit namespace" in { @@ -183,6 +184,21 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { } } + it should "get trigger with updated field" in { + implicit val tid = transid() + val trigger = WhiskTrigger(namespace, aname(), Parameters("x", "b")) + put(entityStore, trigger) + + // `updated` field should be compared with a document in DB + val t = get(entityStore, trigger.docid, WhiskTrigger) + + Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check { + status should be(OK) + val response = responseAs[WhiskTrigger] + response should be(trigger copy (updated = t.updated)) + } + } + it should "report Conflict if the name was of a different type" in { implicit val tid = transid() val rule = WhiskRule( @@ -217,7 +233,7 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { deleteTrigger(trigger.docid) status should be(OK) val response = responseAs[WhiskTrigger] - response should be(trigger.withoutRules) + checkWhiskEntityResponse(response, trigger.withoutRules) } } @@ -229,7 +245,7 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { deleteTrigger(trigger.docid) status should be(OK) val response = responseAs[WhiskTrigger] - response should be(trigger.withoutRules) + checkWhiskEntityResponse(response, trigger.withoutRules) } } @@ -323,11 +339,14 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { deleteTrigger(trigger.docid) status should be(OK) val response = responseAs[WhiskTrigger] - response should be(WhiskTrigger( - trigger.namespace, - trigger.name, - trigger.parameters, - version = trigger.version.upPatch).withoutRules) + checkWhiskEntityResponse( + response, + WhiskTrigger( + trigger.namespace, + trigger.name, + trigger.parameters, + version = trigger.version.upPatch, + updated = dummyInstant).withoutRules) } } @@ -432,8 +451,7 @@ class TriggersApiTests extends ControllerTestCommon with WhiskTriggersApi { Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check { val response = responseAs[WhiskTrigger] status should be(OK) - - response should be(trigger.toWhiskTrigger) + checkWhiskEntityResponse(response, trigger.toWhiskTrigger) } } diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala index 6f9a322ca1b..42fb0d0dfda 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala @@ -431,7 +431,8 @@ class SchemaTests extends FlatSpec with BeforeAndAfter with ExecHelpers with Mat "parameters" -> Parameters().toJson, "version" -> SemVer().toJson, "publish" -> JsFalse, - "annotations" -> Parameters().toJson) + "annotations" -> Parameters().toJson, + "updated" -> pkg.updated.toEpochMilli.toJson) } it should "serialize and deserialize package binding" in { @@ -443,7 +444,8 @@ class SchemaTests extends FlatSpec with BeforeAndAfter with ExecHelpers with Mat "parameters" -> Parameters().toJson, "version" -> SemVer().toJson, "publish" -> JsFalse, - "annotations" -> Parameters().toJson) + "annotations" -> Parameters().toJson, + "updated" -> pkg.updated.toEpochMilli.toJson) //val legacyPkgAsJson = JsObject(pkgAsJson.fields + ("binding" -> JsObject("namespace" -> "x".toJson, "name" -> "y".toJson))) WhiskPackage.serdes.write(pkg) shouldBe pkgAsJson WhiskPackage.serdes.read(pkgAsJson) shouldBe pkg diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala index e473070b97d..3b10d766333 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala @@ -186,7 +186,8 @@ class WhiskEntityTests extends FlatSpec with ExecHelpers with Matchers { | "timeout": 60000, | "memory": 256 | }, - | "namespace": "namespace" + | "namespace": "namespace", + | "updated": 1546268400000 |}""".stripMargin.parseJson val action = WhiskDocumentReader.read(manifest[WhiskAction], json) @@ -213,7 +214,8 @@ class WhiskEntityTests extends FlatSpec with ExecHelpers with Matchers { | "timeout": 60000, | "memory": 256 | }, - | "namespace": "namespace" + | "namespace": "namespace", + | "updated": 1546268400000 |}""".stripMargin.parseJson val action = WhiskDocumentReader.read(manifest[WhiskAction], json) assertType(action.asInstanceOf[WhiskEntity], "action")