| /* |
| * Copyright 2015-2016 IBM Corporation |
| * |
| * Licensed 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 whisk.core.controller.test |
| |
| import scala.language.postfixOps |
| |
| import org.junit.runner.RunWith |
| import org.scalatest.junit.JUnitRunner |
| import spray.http.StatusCodes._ |
| import spray.httpx.SprayJsonSupport._ |
| import spray.json.DefaultJsonProtocol._ |
| import spray.json._ |
| import whisk.core.controller.WhiskRulesApi |
| import whisk.core.entity._ |
| import whisk.core.entity.test.OldWhiskTrigger |
| import whisk.http.ErrorResponse |
| import scala.language.postfixOps |
| import whisk.core.entity.test.OldWhiskRule |
| import whisk.http.Messages |
| |
| /** |
| * Tests Rules API. |
| * |
| * Unit tests of the controller service as a standalone component. |
| * These tests exercise a fresh instance of the service object in memory -- these |
| * tests do NOT communication with a whisk deployment. |
| * |
| * |
| * @Idioglossia |
| * "using Specification DSL to write unit tests, as in should, must, not, be" |
| * "using Specs2RouteTest DSL to chain HTTP requests for unit testing, as in ~>" |
| */ |
| @RunWith(classOf[JUnitRunner]) |
| class RulesApiTests extends ControllerTestCommon with WhiskRulesApi { |
| |
| /** Rules API tests */ |
| behavior of "Rules API" |
| |
| val creds = WhiskAuth(Subject(), AuthKey()).toIdentity |
| val namespace = EntityPath(creds.subject.asString) |
| def aname() = MakeName.next("rules_tests") |
| def afullname(namespace: EntityPath, name: String) = FullyQualifiedEntityName(namespace, EntityName(name)) |
| val collectionPath = s"/${EntityPath.DEFAULT}/${collection.path}" |
| val activeStatus = s"""{"status":"${Status.ACTIVE}"}""".parseJson.asJsObject |
| val inactiveStatus = s"""{"status":"${Status.INACTIVE}"}""".parseJson.asJsObject |
| val parametersLimit = Parameters.sizeLimit |
| |
| //// GET /rules |
| it should "list rules by default/explicit namespace" in { |
| implicit val tid = transid() |
| |
| val rules = (1 to 2).map { i => |
| WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), afullname(namespace, "bogus action")) |
| }.toList |
| rules foreach { put(entityStore, _) } |
| waitOnView(entityStore, WhiskRule, namespace, 2) |
| Get(s"$collectionPath") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[List[JsObject]] |
| rules.length should be(response.length) |
| rules forall { r => response contains r.summaryAsJson } should be(true) |
| } |
| |
| // it should "list trirulesggers with explicit namespace owned by subject" in { |
| Get(s"/$namespace/${collection.path}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[List[JsObject]] |
| rules.length should be(response.length) |
| rules forall { r => response contains r.summaryAsJson } should be(true) |
| } |
| |
| // it should "reject list rules with explicit namespace not owned by subject" in { |
| val auser = WhiskAuth(Subject(), AuthKey()).toIdentity |
| Get(s"/$namespace/${collection.path}") ~> sealRoute(routes(auser)) ~> check { |
| status should be(Forbidden) |
| } |
| } |
| |
| //?docs disabled |
| ignore should "list rules by default namespace with full docs" in { |
| implicit val tid = transid() |
| |
| val rules = (1 to 2).map { i => |
| WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), afullname(namespace, "bogus action")) |
| }.toList |
| rules foreach { put(entityStore, _) } |
| waitOnView(entityStore, WhiskRule, namespace, 2) |
| Get(s"$collectionPath?docs=true") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[List[WhiskRule]] |
| rules.length should be(response.length) |
| rules forall { r => response contains r } should be(true) |
| } |
| } |
| |
| //// GET /rule/name |
| it should "get rule by name in default/explicit namespace" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), afullname(namespace, "bogus action")) |
| put(entityStore, rule) |
| Get(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| |
| // it should "get trigger by name in explicit namespace owned by subject" in |
| Get(s"/$namespace/${collection.path}/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| |
| // it should "reject get trigger by name in explicit namespace not owned by subject" in |
| val auser = WhiskAuth(Subject(), AuthKey()).toIdentity |
| Get(s"/$namespace/${collection.path}/${rule.name}") ~> sealRoute(routes(auser)) ~> check { |
| status should be(Forbidden) |
| } |
| } |
| |
| it should "reject get of non existent rule" in { |
| implicit val tid = transid() |
| |
| Get(s"$collectionPath/xxx") ~> sealRoute(routes(creds)) ~> check { |
| status should be(NotFound) |
| } |
| } |
| |
| it should "get rule with active state in trigger" 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) |
| |
| Get(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| } |
| } |
| |
| it should "get rule with no rule-entries in trigger" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, EntityName("get_rule_with_empty_trigger trigger")) |
| val rule = WhiskRule(namespace, EntityName("get_rule_with_empty_trigger"), trigger.fullyQualifiedName(false), afullname(namespace, "an action")) |
| |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| |
| Get(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| it should "report Conflict if the name was of a different type" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| |
| put(entityStore, trigger) |
| |
| Get(s"/$namespace/${collection.path}/${trigger.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(Conflict) |
| } |
| } |
| |
| // DEL /rules/name |
| it should "not reject delete rule in state active" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, EntityName("reject_delete_rule_active"), FullyQualifiedEntityName(namespace, aname()), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name, rules = Some { |
| Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE)) |
| }) |
| |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| |
| Delete(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| deleteTrigger(trigger.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| it should "delete rule in state inactive" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), FullyQualifiedEntityName(namespace, aname()), FullyQualifiedEntityName(namespace, aname())) |
| val triggerLink = ReducedRule(rule.action, Status.INACTIVE) |
| val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name, rules = Some(Map(rule.fullyQualifiedName(false) -> triggerLink))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Delete(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(OK) |
| t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe None |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| it should "delete rule in state inactive even if the trigger has been deleted" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, EntityName("delete_rule_inactive"), afullname(namespace, "a trigger"), afullname(namespace, "an action")) |
| |
| put(entityStore, rule) |
| |
| Delete(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| it should "delete rule in state inactive even if the trigger has no reference to the rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), FullyQualifiedEntityName(namespace, aname()), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Delete(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| deleteTrigger(trigger.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| //// PUT /rules/name |
| it should "create rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), FullyQualifiedEntityName(namespace, aname()), FullyQualifiedEntityName(namespace, aname())) |
| val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name) |
| val action = WhiskAction(rule.action.path, rule.action.name, Exec.js("??")) |
| val content = WhiskRulePut(Some(rule.trigger), Some(rule.action)) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${rule.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) |
| } |
| } |
| |
| it should "create rule without fully qualifying name" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), FullyQualifiedEntityName(namespace, aname()), FullyQualifiedEntityName(namespace, aname())) |
| val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name) |
| val action = WhiskAction(rule.action.path, rule.action.name, Exec.js("??")) |
| val content = JsObject("trigger" -> JsString(s"/_/${trigger.name.asString}"), "action" -> JsString(s"/_/${action.name.asString}")) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${rule.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) |
| } |
| } |
| |
| it should "reject create rule without namespace in referenced entities" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), FullyQualifiedEntityName(namespace, aname()), FullyQualifiedEntityName(namespace, aname())) |
| val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name) |
| val action = WhiskAction(rule.action.path, rule.action.name, Exec.js("??")) |
| val contentT = JsObject("trigger" -> trigger.name.toJson, "action" -> action.fullyQualifiedName(false).toDocId.toJson) |
| val contentA = JsObject("action" -> action.name.toJson, "trigger" -> trigger.fullyQualifiedName(false).toDocId.toJson) |
| |
| Put(s"$collectionPath/${rule.name}", contentT) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] shouldBe s"The request content was malformed:\nrequirement failed: ${Messages.malformedFullyQualifiedEntityName}" |
| } |
| |
| Put(s"$collectionPath/${rule.name}", contentA) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] shouldBe s"The request content was malformed:\nrequirement failed: ${Messages.malformedFullyQualifiedEntityName}" |
| } |
| } |
| |
| it should "create rule with an action in a package" in { |
| implicit val tid = transid() |
| |
| val provider = WhiskPackage(namespace, aname(), publish = true) |
| val action = WhiskAction(provider.fullPath, aname(), Exec.js("??")) |
| val trigger = WhiskTrigger(namespace, aname()) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), action.fullyQualifiedName(false)) |
| val content = WhiskRulePut(Some(rule.trigger), Some(rule.action)) |
| |
| put(entityStore, provider) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${rule.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) |
| } |
| } |
| |
| it should "create rule with an action in a binding" in { |
| implicit val tid = transid() |
| |
| val provider = WhiskPackage(namespace, aname(), publish = true) |
| val reference = WhiskPackage(namespace, aname(), provider.bind) |
| val action = WhiskAction(provider.fullPath, aname(), Exec.js("??")) |
| val trigger = WhiskTrigger(namespace, aname()) |
| val actionReference = reference.binding.map(b => b.namespace.addPath(b.name)).get |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), FullyQualifiedEntityName(actionReference, action.name)) |
| val content = WhiskRulePut(Some(rule.trigger), Some(rule.action)) |
| |
| put(entityStore, provider) |
| put(entityStore, reference) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${rule.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE) |
| } |
| } |
| |
| it should "reject create rule with annotations which are too big" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| |
| val keys: List[Long] = List.range(Math.pow(10, 9) toLong, (Parameters.sizeLimit.toBytes / 20 + Math.pow(10, 9) + 2) toLong) |
| val annotations = keys map { key => |
| Parameters(key.toString, "a" * 10) |
| } reduce (_ ++ _) |
| val content = s"""{"trigger":"${trigger.name}","action":"${action.name}","annotations":$annotations}""".parseJson.asJsObject |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${aname()}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(RequestEntityTooLarge) |
| responseAs[String] should include { |
| Messages.entityTooBig(SizeError(WhiskEntity.annotationsFieldName, annotations.size, Parameters.sizeLimit)) |
| } |
| } |
| } |
| |
| it should "reject update rule with annotations which are too big" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), action.fullyQualifiedName(false)) |
| |
| val keys: List[Long] = List.range(Math.pow(10, 9) toLong, (Parameters.sizeLimit.toBytes / 20 + Math.pow(10, 9) + 2) toLong) |
| val annotations = keys map { key => |
| Parameters(key.toString, "a" * 10) |
| } reduce (_ ++ _) |
| val content = s"""{"trigger":"${trigger.name}","action":"${action.name}","annotations":$annotations}""".parseJson.asJsObject |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(RequestEntityTooLarge) |
| responseAs[String] should include { |
| Messages.entityTooBig(SizeError(WhiskEntity.annotationsFieldName, annotations.size, Parameters.sizeLimit)) |
| } |
| } |
| } |
| |
| it should "reject rule if action does not exist" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val content = WhiskRulePut(Some(trigger.fullyQualifiedName(false)), Some(afullname(namespace, "bogus action"))) |
| |
| put(entityStore, trigger) |
| |
| Put(s"$collectionPath/xxx", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${content.action.get.qualifiedNameWithLeadingSlash} does not exist" |
| } |
| } |
| |
| it should "reject rule if trigger does not exist" in { |
| implicit val tid = transid() |
| |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val content = WhiskRulePut(Some(afullname(namespace, "bogus trigger")), Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/xxx", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${content.trigger.get.qualifiedNameWithLeadingSlash} does not exist" |
| } |
| } |
| |
| it should "reject rule if neither action or trigger do not exist" in { |
| implicit val tid = transid() |
| |
| val content = WhiskRulePut(Some(afullname(namespace, "bogus trigger")), Some(afullname(namespace, "bogus action"))) |
| |
| Put(s"$collectionPath/xxx", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String].contains("does not exist") should be(true) |
| } |
| } |
| |
| it should "update rule with new trigger and action at once" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), afullname(namespace, "bogus action")) |
| val content = WhiskRulePut(Some(trigger.fullyQualifiedName(false)), Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| |
| t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| it should "update rule with a new action while passing the same trigger" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), afullname(namespace, "bogus action")) |
| val content = WhiskRulePut(Some(trigger.fullyQualifiedName(false)), Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| it should "update rule with just a new action" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), afullname(namespace, "bogus action")) |
| val content = WhiskRulePut(action = Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| it should "update rule with just a new trigger" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), action.fullyQualifiedName(false)) |
| val content = WhiskRulePut(trigger = Some(trigger.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe a[Some[_]] |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| it should "update rule when no new content is provided" in { |
| implicit val tid = transid() |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), action.fullyQualifiedName(false)) |
| val content = WhiskRulePut(None, None, None, None, None) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| deleteTrigger(trigger.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| it should "reject update rule if trigger does not exist" in { |
| implicit val tid = transid() |
| |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), action.fullyQualifiedName(false)) |
| val content = WhiskRulePut(action = Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, action) |
| put(entityStore, rule) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${rule.trigger.qualifiedNameWithLeadingSlash} does not exist" |
| } |
| } |
| |
| it should "reject update rule if action does not exist" in { |
| implicit val tid = transid() |
| |
| val trigger = WhiskTrigger(namespace, aname()) |
| val rule = WhiskRule(namespace, aname(), trigger.fullyQualifiedName(false), afullname(namespace, "bogus action")) |
| val content = WhiskRulePut(trigger = Some(trigger.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${rule.action.qualifiedNameWithLeadingSlash} does not exist" |
| } |
| } |
| |
| it should "reject update rule if neither trigger or action exist" in { |
| implicit val tid = transid() |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "bogus trigger"), afullname(namespace, "bogus action")) |
| val content = WhiskRulePut(None, None, None, None, None) |
| |
| put(entityStore, rule) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] should { |
| include(s"${rule.action.qualifiedNameWithLeadingSlash} does not exist") or |
| include(s"${rule.trigger.qualifiedNameWithLeadingSlash} does not exist") |
| } |
| } |
| } |
| |
| it should "not reject update rule in state active" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "a trigger"), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name, rules = Some { |
| Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE)) |
| }) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val content = WhiskRulePut(Some(trigger.fullyQualifiedName(false)), Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| status should be(OK) |
| t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false)) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.fullyQualifiedName(false), action.fullyQualifiedName(false), version = SemVer().upPatch)) |
| } |
| } |
| |
| //// POST /rules/name |
| it should "do nothing to disable already disabled rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "a trigger"), afullname(namespace, "an action")) |
| |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", inactiveStatus) ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| } |
| } |
| |
| it should "do nothing to enable already enabled rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name, rules = Some { |
| Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE)) |
| }) |
| |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", activeStatus) ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| } |
| } |
| |
| it should "reject post with status undefined" in { |
| implicit val tid = transid() |
| |
| Post(s"$collectionPath/xyz") ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| } |
| } |
| |
| it should "reject post with invalid status" in { |
| implicit val tid = transid() |
| |
| val badStatus = s"""{"status":"xxx"}""".parseJson.asJsObject |
| |
| Post(s"$collectionPath/xyz", badStatus) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| } |
| } |
| |
| it should "activate rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name, rules = Some { |
| Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.INACTIVE)) |
| }) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", activeStatus) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(OK) |
| |
| t.rules.get(rule.fullyQualifiedName(false)).status should be(Status.ACTIVE) |
| } |
| } |
| |
| it should "activate rule without rule in trigger" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", activeStatus) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(OK) |
| t.rules.get(rule.fullyQualifiedName(false)).status should be(Status.ACTIVE) |
| } |
| } |
| |
| it should "reject rule activation, if the trigger is absent" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, "an action")) |
| |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", activeStatus) ~> sealRoute(routes(creds)) ~> check { |
| status should be(NotFound) |
| } |
| } |
| |
| it should "deactivate rule" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, "an action")) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name, rules = Some { |
| Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE)) |
| }) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", inactiveStatus) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(OK) |
| t.rules.get(rule.fullyQualifiedName(false)).status should be(Status.INACTIVE) |
| } |
| } |
| |
| // invalid resource |
| it should "reject invalid resource" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, "a trigger"), afullname(namespace, "an action")) |
| |
| put(entityStore, rule) |
| |
| Get(s"$collectionPath/${rule.name}/bar") ~> sealRoute(routes(creds)) ~> check { |
| status should be(NotFound) |
| } |
| } |
| |
| // migration path |
| it should "handle a rule with the old schema gracefully" in { |
| implicit val tid = transid() |
| |
| val rule = OldWhiskRule(namespace, aname(), EntityName("a trigger"), EntityName("an action"), Status.ACTIVE) |
| |
| put(entityStore, rule) |
| |
| Get(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(OK) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.toWhiskRule.withStatus(Status.INACTIVE)) |
| } |
| } |
| |
| it should "create rule even if the attached trigger has the old schema" in { |
| implicit val tid = transid() |
| |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, aname().name)) |
| val trigger = OldWhiskTrigger(namespace, rule.trigger.name) |
| |
| val action = WhiskAction(namespace, rule.action.name, Exec.js("??")) |
| val content = WhiskRulePut(Some(rule.trigger), Some(rule.action)) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${rule.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| deleteRule(rule.docid) |
| |
| 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)) |
| } |
| } |
| |
| it should "activate rule even if it is still in the old schema" in { |
| implicit val tid = transid() |
| |
| val ruleNameQualified = FullyQualifiedEntityName(namespace, aname()) |
| val triggerName = aname() |
| val actionName = FullyQualifiedEntityName(namespace, aname()) |
| val rule = OldWhiskRule(namespace, ruleNameQualified.name, triggerName, actionName.name, Status.ACTIVE) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(ruleNameQualified -> ReducedRule(actionName, Status.INACTIVE)))) |
| |
| put(entityStore, trigger, false) |
| put(entityStore, rule) |
| |
| Post(s"$collectionPath/${rule.name}", activeStatus) ~> sealRoute(routes(creds)) ~> check { |
| val t = get(entityStore, trigger.docid, WhiskTrigger) |
| deleteTrigger(t.docid) |
| |
| status should be(OK) |
| |
| t.rules.get(ruleNameQualified).status should be(Status.ACTIVE) |
| } |
| } |
| |
| it should "report proper error when record is corrupted on delete" in { |
| implicit val tid = transid() |
| val entity = BadEntity(namespace, aname()) |
| put(entityStore, entity) |
| |
| Delete(s"$collectionPath/${entity.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(InternalServerError) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| } |
| |
| it should "report proper error when record is corrupted on get" in { |
| implicit val tid = transid() |
| val entity = BadEntity(namespace, aname()) |
| put(entityStore, entity) |
| |
| Get(s"$collectionPath/${entity.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(InternalServerError) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| } |
| |
| it should "report proper error when record is corrupted on put" in { |
| implicit val tid = transid() |
| val entity = BadEntity(namespace, aname()) |
| put(entityStore, entity) |
| |
| val content = WhiskRulePut() |
| Put(s"$collectionPath/${entity.name}", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(InternalServerError) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| } |
| |
| it should "report proper error when action record is corrupted on put" in { |
| implicit val tid = transid() |
| val tentity = BadEntity(namespace, aname()) |
| val aentity = BadEntity(namespace, aname()) |
| val rule = WhiskRule(namespace, aname(), afullname(namespace, aname().name), afullname(namespace, aname().name)) |
| val trigger = WhiskTrigger(namespace, rule.trigger.name) |
| val action = WhiskAction(namespace, rule.action.name, Exec.js("??")) |
| |
| val contenta = WhiskRulePut(Some(tentity.fullyQualifiedName(false)), Some(aentity.fullyQualifiedName(false))) |
| val contentb = WhiskRulePut(Some(trigger.fullyQualifiedName(false)), Some(aentity.fullyQualifiedName(false))) |
| val contentc = WhiskRulePut(Some(tentity.fullyQualifiedName(false)), Some(action.fullyQualifiedName(false))) |
| |
| put(entityStore, tentity) |
| put(entityStore, aentity) |
| put(entityStore, trigger) |
| put(entityStore, action) |
| |
| Put(s"$collectionPath/${aname()}", contenta) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| |
| Put(s"$collectionPath/${aname()}", contentb) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| |
| Put(s"$collectionPath/${aname()}", contentc) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[ErrorResponse].error shouldBe Messages.corruptedEntity |
| } |
| } |
| } |