Add test for rule from trigger/action with and without `_` as namespace.

This disallows creating rule from trigger and action without having to fully qualify the entity names in the API request.

- some refactoring to make better use of ScalaTest matchers.
diff --git a/tests/src/whisk/core/controller/test/RulesApiTests.scala b/tests/src/whisk/core/controller/test/RulesApiTests.scala
index 5f61171..04c99aa 100644
--- a/tests/src/whisk/core/controller/test/RulesApiTests.scala
+++ b/tests/src/whisk/core/controller/test/RulesApiTests.scala
@@ -23,8 +23,7 @@
 import spray.http.StatusCodes._
 import spray.httpx.SprayJsonSupport._
 import spray.json.DefaultJsonProtocol._
-import spray.json.JsObject
-import spray.json.pimpString
+import spray.json._
 import whisk.core.controller.WhiskRulesApi
 import whisk.core.entity._
 import whisk.core.entity.test.OldWhiskTrigger
@@ -286,6 +285,49 @@
         }
     }
 
+    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()}"), "action" -> JsString(s"/_/${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)
+            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()
 
diff --git a/tests/src/whisk/core/entity/test/SchemaTests.scala b/tests/src/whisk/core/entity/test/SchemaTests.scala
index 3be9a20..66c56cd 100644
--- a/tests/src/whisk/core/entity/test/SchemaTests.scala
+++ b/tests/src/whisk/core/entity/test/SchemaTests.scala
@@ -52,10 +52,8 @@
     }
 
     it should "reject malformed ids" in {
-        Seq(null, "", " ", ":", " : ", " :", ": ", "a:b").foreach { i =>
-            intercept[IllegalArgumentException] {
-                AuthKey(i)
-            }
+        Seq(null, "", " ", ":", " : ", " :", ": ", "a:b").foreach {
+            i => an[IllegalArgumentException] should be thrownBy AuthKey(i)
         }
     }
 
@@ -79,43 +77,38 @@
         DocRevision.serdes.read(JsString("a")) shouldBe DocRevision("a")
         DocRevision.serdes.read(JsString(" a")) shouldBe DocRevision("a")
         DocRevision.serdes.read(JsString("a ")) shouldBe DocRevision("a")
-        intercept[DeserializationException] {
-            DocRevision.serdes.read(JsNumber(1))
-        }
+        a[DeserializationException] should be thrownBy DocRevision.serdes.read(JsNumber(1))
     }
 
     it should "reject malformed doc info" in {
-        Seq(null, "", " ").foreach { i =>
-            intercept[IllegalArgumentException] {
-                DocInfo(i)
-            }
+        Seq(null, "", " ").foreach {
+            i => an[IllegalArgumentException] should be thrownBy DocInfo(i)
         }
     }
 
     it should "reject malformed doc ids" in {
-        Seq(null, "", " ").foreach { i =>
-            intercept[IllegalArgumentException] {
-                DocId(i)
-            }
+        Seq(null, "", " ").foreach {
+            i => an[IllegalArgumentException] should be thrownBy DocId(i)
         }
     }
 
-    behavior of "Namespace"
+    behavior of "EntityPath"
 
     it should "accept well formed paths" in {
         val paths = Seq("/a", "//a", "//a//", "//a//b//c", "//a//b/c//", "a", "a/b", "a/b/", "a@b.c", "a@b.c/", "a@b.c/d", "_a/", "_ _", "a/b/c")
         val expected = Seq("a", "a", "a", "a/b/c", "a/b/c", "a", "a/b", "a/b", "a@b.c", "a@b.c", "a@b.c/d", "_a", "_ _", "a/b/c")
-        val spaces = paths.zip(expected).foreach { p =>
-            assert(EntityPath(p._1).namespace == p._2)
+        val spaces = paths.zip(expected).foreach {
+            p => EntityPath(p._1).namespace shouldBe p._2
         }
+
+        EntityPath.DEFAULT.addpath(EntityPath("a")).toString shouldBe "_/a"
+        EntityPath.DEFAULT.addpath(EntityPath("a/b")).toString shouldBe "_/a/b"
     }
 
     it should "reject malformed paths" in {
         val paths = Seq(null, "", " ", "a/ ", "a/b/c ", " xxx", "xxx ", " xxx", "xxx/ ", "/", " /", "/ ", "//", "///", " / / / ", "a/b/ c", "a/ /b", " a/ b")
-        paths.foreach { p =>
-            val thrown = intercept[IllegalArgumentException] {
-                EntityPath(p)
-            }
+        paths.foreach {
+            p => an[IllegalArgumentException] should be thrownBy EntityPath(p)
         }
     }
 
@@ -130,10 +123,8 @@
 
     it should "reject malformed names" in {
         val paths = Seq(null, "", " ", " xxx", "xxx ", "/", " /", "/ ", "0 ", "_ ", "a  ", "a \t", "a\n")
-        paths.foreach { p =>
-            val thrown = intercept[IllegalArgumentException] {
-                EntityName(p)
-            }
+        paths.foreach {
+            p => an[IllegalArgumentException] should be thrownBy EntityName(p)
         }
     }
 
@@ -145,15 +136,26 @@
             JsObject("path" -> "a".toJson, "name" -> "b".toJson),
             JsObject("path" -> "a".toJson, "name" -> "b".toJson, "version" -> "0.0.1".toJson),
             JsString("a/b"),
-            JsObject("namespace" -> "a".toJson, "name" -> "b".toJson))
+            JsString("n/a/b"),
+            JsString("/a/b"),
+            JsString("/n/a/b"),
+            JsString("b")) //JsObject("namespace" -> "a".toJson, "name" -> "b".toJson))
 
         FullyQualifiedEntityName.serdes.read(names(0)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"))
         FullyQualifiedEntityName.serdes.read(names(1)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"), Some(SemVer()))
         FullyQualifiedEntityName.serdes.read(names(2)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"))
+        FullyQualifiedEntityName.serdes.read(names(3)) shouldBe FullyQualifiedEntityName(EntityPath("n/a"), EntityName("b"))
+        FullyQualifiedEntityName.serdes.read(names(4)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"))
+        FullyQualifiedEntityName.serdes.read(names(5)) shouldBe FullyQualifiedEntityName(EntityPath("n/a"), EntityName("b"))
+        a[DeserializationException] should be thrownBy FullyQualifiedEntityName.serdes.read(names(6))
 
         a[DeserializationException] should be thrownBy FullyQualifiedEntityName.serdesAsDocId.read(names(0))
         a[DeserializationException] should be thrownBy FullyQualifiedEntityName.serdesAsDocId.read(names(1))
         FullyQualifiedEntityName.serdesAsDocId.read(names(2)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"))
+        FullyQualifiedEntityName.serdesAsDocId.read(names(3)) shouldBe FullyQualifiedEntityName(EntityPath("n/a"), EntityName("b"))
+        FullyQualifiedEntityName.serdesAsDocId.read(names(4)) shouldBe FullyQualifiedEntityName(EntityPath("a"), EntityName("b"))
+        FullyQualifiedEntityName.serdesAsDocId.read(names(5)) shouldBe FullyQualifiedEntityName(EntityPath("n/a"), EntityName("b"))
+        a[DeserializationException] should be thrownBy FullyQualifiedEntityName.serdesAsDocId.read(names(6))
     }
 
     behavior of "Binding"
@@ -259,18 +261,10 @@
     }
 
     it should "reject negative values" in {
-        intercept[IllegalArgumentException] {
-            SemVer(-1, 0, 0)
-        }
-        intercept[IllegalArgumentException] {
-            SemVer(0, -1, 0)
-        }
-        intercept[IllegalArgumentException] {
-            SemVer(0, 0, -1)
-        }
-        intercept[IllegalArgumentException] {
-            SemVer(0, 0, 0)
-        }
+        an[IllegalArgumentException] should be thrownBy SemVer(-1, 0, 0)
+        an[IllegalArgumentException] should be thrownBy SemVer(0, -1, 0)
+        an[IllegalArgumentException] should be thrownBy SemVer(0, 0, -1)
+        an[IllegalArgumentException] should be thrownBy SemVer(0, 0, 0)
     }
 
     behavior of "Exec"
@@ -337,15 +331,9 @@
     }
 
     it should "reject null code/image arguments" in {
-        intercept[IllegalArgumentException] {
-            Exec.serdes.read(null)
-        }
-        intercept[DeserializationException] {
-            Exec.serdes.read("{}" parseJson)
-        }
-        intercept[DeserializationException] {
-            Exec.serdes.read(JsString(""))
-        }
+        an[IllegalArgumentException] should be thrownBy Exec.serdes.read(null)
+        a[DeserializationException] should be thrownBy Exec.serdes.read("{}" parseJson)
+        a[DeserializationException] should be thrownBy Exec.serdes.read(JsString(""))
     }
 
     it should "serialize to json" in {
@@ -385,35 +373,20 @@
             JsObject("KEY" -> "k".toJson, "VALUE" -> "v".toJson),
             JsObject("key" -> "k".toJson, "value" -> 0.toJson))
 
-        params.foreach { p =>
-            val thrown = intercept[DeserializationException] {
-                Parameters.serdes.read(p)
-            }
+        params.foreach {
+            p => a[DeserializationException] should be thrownBy Parameters.serdes.read(p)
         }
     }
 
     it should "reject undefined key" in {
-        intercept[DeserializationException] {
-            Parameters.serdes.read(null: JsValue)
-        }
-        intercept[IllegalArgumentException] {
-            Parameters(null, null: String)
-        }
-        intercept[IllegalArgumentException] {
-            Parameters("", null: JsValue)
-        }
-        intercept[IllegalArgumentException] {
-            Parameters(" ", null: String)
-        }
-        intercept[IllegalArgumentException] {
-            Parameters(null, "")
-        }
-        intercept[IllegalArgumentException] {
-            Parameters(null, " ")
-        }
-        intercept[IllegalArgumentException] {
-            Parameters(null)
-        }
+        a[DeserializationException] should be thrownBy Parameters.serdes.read(null: JsValue)
+        an[IllegalArgumentException] should be thrownBy Parameters(null, null: String)
+        an[IllegalArgumentException] should be thrownBy Parameters("", null: JsValue)
+        an[IllegalArgumentException] should be thrownBy Parameters(" ", null: String)
+        an[IllegalArgumentException] should be thrownBy Parameters(null, "")
+        an[IllegalArgumentException] should be thrownBy Parameters(null, " ")
+        an[IllegalArgumentException] should be thrownBy Parameters(null)
+
     }
 
     it should "serialize to json" in {
@@ -453,10 +426,8 @@
             JsObject("timeout" -> JsNull, "memory" -> JsNull),
             JsObject("timeout" -> TimeLimit.STD_DURATION.toMillis.toString.toJson, "memory" -> MemoryLimit.STD_MEMORY.toMB.toInt.toString.toJson))
 
-        limits.foreach { p =>
-            val thrown = intercept[DeserializationException] {
-                ActionLimits.serdes.read(p)
-            }
+        limits.foreach {
+            p => a[DeserializationException] should be thrownBy ActionLimits.serdes.read(p)
         }
     }