| /* |
| * 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 org.junit.runner.RunWith |
| import org.scalatest.junit.JUnitRunner |
| import spray.http.StatusCodes.BadRequest |
| import spray.http.StatusCodes.Conflict |
| import spray.http.StatusCodes.OK |
| import spray.http.StatusCodes.NotFound |
| import spray.http.StatusCodes.RequestEntityTooLarge |
| import spray.httpx.SprayJsonSupport.sprayJsonMarshaller |
| import spray.httpx.SprayJsonSupport.sprayJsonUnmarshaller |
| import spray.json.DefaultJsonProtocol.listFormat |
| import spray.json.DefaultJsonProtocol.RootJsObjectFormat |
| import spray.json.DefaultJsonProtocol.listFormat |
| import spray.json.JsObject |
| import spray.json.pimpString |
| import whisk.core.controller.WhiskRulesApi |
| import whisk.core.entity.EntityName |
| import whisk.core.entity.Exec |
| import whisk.core.entity.EntityPath |
| import whisk.core.entity.Parameters |
| import whisk.core.entity.SemVer |
| import whisk.core.entity.Status |
| import whisk.core.entity.AuthKey |
| import whisk.core.entity.WhiskAuth |
| import whisk.core.entity.Subject |
| import whisk.core.entity.WhiskAction |
| import whisk.core.entity.WhiskEntity |
| import whisk.core.entity.WhiskRule |
| import whisk.core.entity.WhiskRulePut |
| import whisk.core.entity.WhiskTrigger |
| import whisk.http.ErrorResponse |
| import scala.language.postfixOps |
| import whisk.core.entity.WhiskRuleResponse |
| import whisk.core.entity.ReducedRule |
| import whisk.core.entity.test.OldWhiskRule |
| import whisk.core.entity.test.OldWhiskTrigger |
| import whisk.core.entity.WhiskPackage |
| |
| /** |
| * 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()) |
| def aname() = MakeName.next("rules_tests") |
| val collectionPath = s"/${EntityPath.DEFAULT}/${collection.path}" |
| val activeStatus = s"""{"status":"${Status.ACTIVE}"}""".parseJson.asJsObject |
| val inactiveStatus = s"""{"status":"${Status.INACTIVE}"}""".parseJson.asJsObject |
| val entityTooBigRejectionMessage = "request entity too large" |
| val parametersLimit = Parameters.sizeLimit |
| |
| //// GET /rules |
| it should "list rules by default namespace" in { |
| implicit val tid = transid() |
| val rules = (1 to 2).map { i => |
| WhiskRule(namespace, aname(), EntityName("bogus trigger"), EntityName("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) |
| } |
| } |
| |
| //?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(), EntityName("bogus trigger"), EntityName("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" in { |
| implicit val tid = transid() |
| val rule = WhiskRule(namespace, aname(), EntityName("bogus trigger"), EntityName("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 "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 ruleName = EntityName("get_active_rule") |
| val triggerName = EntityName("get_active_rule trigger") |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) -> ReducedRule(namespace, 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 ruleName = EntityName("get_rule_with_empty_trigger") |
| val triggerName = EntityName("get_rule_with_empty_trigger trigger") |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| val trigger = WhiskTrigger(namespace, triggerName) |
| |
| 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 "reject delete rule in state active" in { |
| implicit val tid = transid() |
| val ruleName = EntityName("reject_delete_rule_active") |
| val triggerName = aname() |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) -> ReducedRule(namespace, Status.ACTIVE)))) |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| Delete(s"$collectionPath/${rule.name}") ~> sealRoute(routes(creds)) ~> check { |
| status should be(Conflict) |
| val response = responseAs[ErrorResponse] |
| response.error should be(s"rule status is '${Status.ACTIVE}', must be '${Status.INACTIVE}' to delete") |
| response.code() should be >= 1L |
| } |
| } |
| |
| it should "delete rule in state inactive" in { |
| implicit val tid = transid() |
| val ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val actionName = aname() |
| val actionNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, actionName)) |
| val triggerLink = ReducedRule(actionNameQualified, Status.INACTIVE) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(ruleNameQualified -> triggerLink))) |
| val rule = WhiskRule(namespace, ruleName, triggerName, actionName) |
| 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(ruleNameQualified) 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"), EntityName("a trigger"), EntityName("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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val trigger = WhiskTrigger(namespace, triggerName) |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| 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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val actionName = aname() |
| val actionNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, actionName)) |
| val trigger = WhiskTrigger(namespace, triggerName) |
| val rule = WhiskRule(namespace, ruleName, triggerName, actionName) |
| val action = WhiskAction(namespace, actionName, Exec.js("??")) |
| val content = WhiskRulePut(Some(trigger.name), Some(action.name)) |
| 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(ruleNameQualified) shouldBe ReducedRule(actionNameQualified, Status.ACTIVE) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| } |
| } |
| |
| ignore should "create rule with an action in a package" in { |
| implicit val tid = transid() |
| |
| val provider = WhiskPackage(namespace, aname()) |
| put(entityStore, provider) |
| |
| val actionName = aname() |
| val action = WhiskAction(provider.path, actionName, Exec.js("??")) |
| // TODO: this should be an EntityQName, not an EntityPath |
| val actionNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, action.name)) |
| |
| val triggerName = aname() |
| val trigger = WhiskTrigger(namespace, triggerName) |
| |
| val ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| |
| val rule = WhiskRule(namespace, ruleName, triggerName, actionName) |
| val content = WhiskRulePut(Some(trigger.name), Some(action.name)) |
| |
| 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(ruleNameQualified) shouldBe ReducedRule(actionNameQualified, Status.ACTIVE) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(rule.withStatus(Status.ACTIVE)) |
| } |
| } |
| |
| it should "reject create rule with annotations which are too big" in { |
| implicit val tid = transid() |
| val keys: List[Long] = List.range(Math.pow(10, 9) toLong, (Parameters.sizeLimit.toBytes / 2 / 20 + Math.pow(10, 9) + 2) toLong) |
| val parameters = keys map { key => |
| Parameters(key.toString, "a" * 10) |
| } reduce (_ ++ _) |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val content = s"""{"trigger":"${trigger.name}","action":"${action.name}","annotations":$parameters}""".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) |
| response.entity.toString should include(entityTooBigRejectionMessage) |
| } |
| } |
| |
| it should "reject update rule with annotations which are too big" in { |
| implicit val tid = transid() |
| val keys: List[Long] = List.range(Math.pow(10, 9) toLong, (Parameters.sizeLimit.toBytes / 2 / 20 + Math.pow(10, 9) + 2) toLong) |
| val parameters = keys map { key => |
| Parameters(key.toString, "a" * 10) |
| } reduce (_ ++ _) |
| val trigger = WhiskTrigger(namespace, aname()) |
| val action = WhiskAction(namespace, aname(), Exec.js("??")) |
| val rule = WhiskRule(namespace, aname(), trigger.name, action.name) |
| val content = s"""{"trigger":"${trigger.name}","action":"${action.name}","annotations":$parameters}""".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) |
| response.entity.toString should include(entityTooBigRejectionMessage) |
| } |
| } |
| |
| it should "reject rule if action does not exist" in { |
| implicit val tid = transid() |
| val trigger = WhiskTrigger(namespace, aname()) |
| val content = WhiskRulePut(Some(trigger.name), Some(EntityName("bogus action"))) |
| put(entityStore, trigger) |
| Put(s"$collectionPath/xxx", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${WhiskEntity.qualifiedName(namespace, content.action.get)} 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(EntityName("bogus trigger")), Some(action.name)) |
| put(entityStore, action) |
| Put(s"$collectionPath/xxx", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${WhiskEntity.qualifiedName(namespace, content.trigger.get)} does not exist" |
| } |
| } |
| |
| it should "reject rule if neither action or trigger do not exist" in { |
| implicit val tid = transid() |
| val content = WhiskRulePut(Some(EntityName("bogus trigger")), Some(EntityName("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 updating 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(), EntityName("bogus trigger"), EntityName("bogus action")) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| val content = WhiskRulePut(Some(trigger.name), Some(action.name)) |
| 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(EntityPath(WhiskEntity.qualifiedName(namespace, rule.name))).action should be(EntityPath(WhiskEntity.qualifiedName(namespace, action.name))) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.name, action.name, 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.name, EntityName("bogus action")) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| val content = WhiskRulePut(Some(trigger.name), Some(action.name)) |
| 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(EntityPath(WhiskEntity.qualifiedName(namespace, rule.name))).action should be(EntityPath(WhiskEntity.qualifiedName(namespace, action.name))) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.name, action.name, 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.name, EntityName("bogus action")) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| val content = WhiskRulePut(action = Some(action.name)) |
| 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.map { rules => rules(EntityPath(WhiskEntity.qualifiedName(namespace, rule.name))).action }.get should be(EntityPath(WhiskEntity.qualifiedName(namespace, action.name))) |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.name, action.name, 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.name, action.name) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| val content = WhiskRulePut(trigger = Some(trigger.name)) |
| 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(EntityPath(WhiskEntity.qualifiedName(namespace, rule.name))) shouldBe a[Some[_]] |
| val response = responseAs[WhiskRuleResponse] |
| response should be(WhiskRuleResponse(namespace, rule.name, Status.ACTIVE, trigger.name, action.name, 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.name, action.name) |
| put(entityStore, trigger, false) |
| put(entityStore, action) |
| put(entityStore, rule, false) |
| val content = WhiskRulePut(None, None, None, None, None) |
| 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.name, action.name, 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(), EntityName("bogus trigger"), action.name) |
| put(entityStore, action) |
| put(entityStore, rule) |
| val content = WhiskRulePut(action = Some(action.name)) |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${WhiskEntity.qualifiedName(namespace, EntityName(rule.trigger.name))} 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.name, EntityName("bogus action")) |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| val content = WhiskRulePut(trigger = Some(trigger.name)) |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String] === s"${WhiskEntity.qualifiedName(namespace, EntityName(rule.action.name))} does not exist" |
| } |
| } |
| |
| it should "reject update rule if neither trigger or action exist" in { |
| implicit val tid = transid() |
| val rule = WhiskRule(namespace, aname(), EntityName("bogus trigger"), EntityName("bogus action")) |
| put(entityStore, rule) |
| val content = WhiskRulePut(None, None, None, None, None) |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(BadRequest) |
| responseAs[String].contains("does not exist") should be(true) |
| } |
| } |
| |
| it should "reject update rule in state active" in { |
| implicit val tid = transid() |
| val triggerName = EntityName("a trigger") |
| val ruleName = aname() |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) -> ReducedRule(namespace, Status.ACTIVE)))) |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| put(entityStore, trigger) |
| put(entityStore, rule) |
| val content = WhiskRulePut(publish = Some(!rule.publish)) |
| Put(s"$collectionPath/${rule.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check { |
| status should be(Conflict) |
| } |
| } |
| |
| //// POST /rules/name |
| it should "do nothing to disable already disabled rule" in { |
| implicit val tid = transid() |
| val rule = WhiskRule(namespace, aname(), EntityName("a trigger"), EntityName("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 ruleName = aname() |
| val triggerName = aname() |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) -> ReducedRule(namespace, Status.ACTIVE)))) |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| 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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(ruleNameQualified -> ReducedRule(namespace, 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 "activate rule without rule in trigger" in { |
| implicit val tid = transid() |
| val ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| val trigger = WhiskTrigger(namespace, triggerName) |
| 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 "reject rule activation, if the trigger is absent" in { |
| implicit val tid = transid() |
| val ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val rule = WhiskRule(namespace, ruleName, triggerName, EntityName("an action")) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(ruleNameQualified -> ReducedRule(namespace, 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(ruleNameQualified).status should be(Status.INACTIVE) |
| } |
| } |
| |
| // invalid resource |
| it should "reject invalid resource" in { |
| implicit val tid = transid() |
| val rule = WhiskRule(namespace, aname(), EntityName("a trigger"), EntityName("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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val actionName = aname() |
| val actionNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, actionName)) |
| val trigger = OldWhiskTrigger(namespace, triggerName) |
| val rule = WhiskRule(namespace, ruleName, triggerName, actionName) |
| val action = WhiskAction(namespace, actionName, Exec.js("??")) |
| val content = WhiskRulePut(Some(trigger.name), Some(action.name)) |
| 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(ruleNameQualified) shouldBe ReducedRule(actionNameQualified, 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 ruleName = aname() |
| val ruleNameQualified = EntityPath(WhiskEntity.qualifiedName(namespace, ruleName)) |
| val triggerName = aname() |
| val rule = OldWhiskRule(namespace, ruleName, triggerName, EntityName("an action"), Status.ACTIVE) |
| val trigger = WhiskTrigger(namespace, triggerName, rules = Some(Map(ruleNameQualified -> ReducedRule(namespace, 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) |
| } |
| } |
| } |