support disabling triggers (never delete triggers that won't fire) (#88)

diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index deedc7f..2fddeb5 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 76babd2..ac0c4b9 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Sep 17 00:27:09 CEST 2016
+#Fri Apr 21 22:50:39 EDT 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip
diff --git a/gradlew b/gradlew
index 9aa616c..4453cce 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
 
 ##############################################################################
 ##
@@ -154,16 +154,19 @@
     esac
 fi
 
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
 }
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 
 # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
   cd "$(dirname "$0")"
 fi
 
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/provider/lib/update.js b/provider/lib/update.js
index acb2658..dd5ad4a 100644
--- a/provider/lib/update.js
+++ b/provider/lib/update.js
@@ -50,8 +50,12 @@
                 // number of retries to create a trigger.
                 utils.createTrigger(trigger, utils.retryAttempts)
                 .then(newTrigger => {
-                    logger.info(method, 'Trigger was added and database is confirmed.', newTrigger.id);
+                    newTrigger.status = {
+                        'active': true,
+                        'dateChanged': new Date().toISOString(),
+                    };
                     utils.addTriggerToDB(newTrigger, res);
+                    logger.info(method, 'Trigger was added and database is confirmed.', newTrigger.id);
                 }).catch(err => {
                     logger.error(method, 'Trigger', id, 'could not be created.', err);
                     utils.deleteTrigger(id);
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 3023954..94af602 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -155,44 +155,44 @@
         triggerDB.view('filters', 'only_triggers', {include_docs: true}, function(err, body) {
             if (!err) {
                 body.rows.forEach(function(trigger) {
+                    if (!trigger.doc.status || trigger.doc.status.active === true) {
+                        //check if trigger still exists in whisk db
+                        var triggerObj = that.parseQName(trigger.doc.id);
+                        var host = 'https://' + routerHost + ':' + 443;
+                        var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
+                        var auth = trigger.doc.apikey.split(':');
 
-                    //check if trigger still exists in whisk db
-                    var triggerObj = that.parseQName(trigger.doc.id);
-                    var host = 'https://' + routerHost +':'+ 443;
-                    var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
-                    var auth = trigger.doc.apikey.split(':');
-
-                    logger.info(method, 'Checking if trigger', trigger.doc.id, 'still exists');
-                    request({
-                        method: 'get',
-                        url: triggerURL,
-                        auth: {
-                            user: auth[0],
-                            pass: auth[1]
-                        }
-                    }, function(error, response) {
-                        //delete from database if trigger no longer exists (404)
-                        if (!error && response.statusCode === 404) {
-                            logger.info(method, 'Trigger', trigger.doc.id, 'no longer exists');
-                            that.deleteTriggerFromDB(trigger.doc.id);
-                        }
-                        else {
-                            var cloudantTrigger = that.initTrigger(trigger.doc, trigger.doc.id);
-
-                            // check here for triggers left if none left end here, and don't create
-                            if (cloudantTrigger.maxTriggers === -1 || cloudantTrigger.triggersLeft > 0) {
+                        logger.info(method, 'Checking if trigger', trigger.doc.id, 'still exists');
+                        request({
+                            method: 'get',
+                            url: triggerURL,
+                            auth: {
+                                user: auth[0],
+                                pass: auth[1]
+                            }
+                        }, function (error, response) {
+                            //disable trigger in database if trigger is dead
+                            if (!error && that.shouldDisableTrigger(response.statusCode)) {
+                                var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code when re-creating the trigger';
+                                that.disableTriggerInDB(trigger.doc.id, response.statusCode, message);
+                                logger.error(method, 'trigger', trigger.doc.id, 'has been disabled due to status code:', response.statusCode);
+                            }
+                            else {
+                                var cloudantTrigger = that.initTrigger(trigger.doc, trigger.doc.id);
                                 that.createTrigger(cloudantTrigger, that.retryAttempts)
                                 .then(newTrigger => {
                                     logger.info(method, 'Trigger was added.', newTrigger.id);
                                 }).catch(err => {
-                                    //if feed was not recreated then delete from trigger database
-                                    that.deleteTriggerFromDB(cloudantTrigger.id);
+                                    var message = 'Automatically disabled after receiving an exception when re-creating the trigger';
+                                    that.disableTriggerInDB(cloudantTrigger.id, undefined, message);
+                                    logger.error(method, 'Disabled trigger', cloudantTrigger.id, 'due to exception:', err);
                                 });
-                            } else {
-                                logger.info(method, 'found a trigger with no triggers left to fire off.');
                             }
-                        }
-                    });
+                        });
+                    }
+                    else {
+                        logger.info(method, 'ignoring trigger', trigger.doc._id, 'since it is disabled.');
+                    }
                 });
             } else {
                 logger.error(method, 'could not get latest state from database');
@@ -201,20 +201,6 @@
 
     };
 
-    // Delete a trigger: stop listening for changes and remove it.
-    this.deleteTrigger = function (id) {
-        var method = 'deleteTrigger';
-        var trigger = that.triggers[id];
-        if (trigger) {
-            logger.info(method, 'Stopped cloudant trigger', id, 'listening for changes in database', trigger.dbname);
-            trigger.feed.stop();
-            delete that.triggers[id];
-        } else {
-            logger.info(method, 'trigger', id, 'could not be found in the trigger list.');
-            return false;
-        }
-    };
-
     this.addTriggerToDB = function (trigger, res) {
 
         var method = 'addTriggerToDB';
@@ -224,23 +210,63 @@
                 res.status(200).json(_.omit(trigger, 'feed'));
             } else {
                 that.deleteTrigger(trigger.id);
-                res.status(err.statusCode).json({error: 'Cloudant trigger cannot be created.'});
+                res.status(err.statusCode).json({error: 'Cloudant trigger cannot be created. ' + err});
             }
         });
 
     };
 
+    this.shouldDisableTrigger = function (statusCode) {
+        return ((statusCode >= 400 && statusCode < 500) && [408, 429].indexOf(statusCode) === -1);
+    };
+
+    this.disableTriggerInDB = function (id, statusCode, message) {
+
+        var method = 'disableTriggerInDB';
+
+        triggerDB.get(id, function (err, existing) {
+            if (!err) {
+                if (!existing.status || existing.status.active === true) {
+                    var updatedTrigger = existing;
+                    var status = {
+                        'active': false,
+                        'dateChanged': new Date().toISOString(),
+                        'reason': {'kind': 'AUTO', 'statusCode': statusCode, 'message': message}
+                    };
+                    updatedTrigger.status = status;
+
+                    triggerDB.insert(updatedTrigger, id, function (err) {
+                        if (err) {
+                            logger.error(method, 'there was an error while disabling', id, 'in database. ' + err);
+                        }
+                        else {
+                            that.deleteTrigger(id);
+                            logger.info(method, 'trigger', id, 'successfully disabled in database');
+                        }
+                    });
+                }
+            }
+            else {
+                if (err.statusCode === 404) {
+                    logger.error(method, 'there was no trigger with id', id, 'in database.', err.error);
+                } else {
+                    logger.error(method, 'there was an error while getting', id, 'from database', err);
+                }
+            }
+        });
+    };
+
     this.deleteTriggerFromDB = function (id, res) {
 
         var method = 'deleteTriggerFromDB';
 
-        triggerDB.get(id, function(err, body) {
+        triggerDB.get(id, function(err, existing) {
             if (!err) {
-                triggerDB.destroy(body._id, body._rev, function(err) {
+                triggerDB.destroy(existing._id, existing._rev, function(err) {
                     if (err) {
-                        logger.error(method, 'there was an error while deleting', id, 'from database');
+                        logger.error(method, 'there was an error while deleting', id, 'from database. ' + err);
                         if (res) {
-                            res.status(err.statusCode).json({ error: 'Cloudant data trigger ' + id  + 'cannot be deleted.' } );
+                            res.status(err.statusCode).json({ error: 'Cloudant data trigger ' + id  + 'cannot be deleted. ' + err } );
                         }
                     } else {
                         that.deleteTrigger(id);
@@ -260,7 +286,7 @@
                 } else {
                     logger.error(method, 'there was an error while getting', id, 'from database', err);
                     if (res) {
-                        res.status(err.statusCode).json({ error: 'Cloudant data trigger ' + id  + ' cannot be deleted.' } );
+                        res.status(err.statusCode).json({ error: 'Cloudant data trigger ' + id  + ' cannot be deleted. ' + err } );
                     }
                 }
             }
@@ -268,6 +294,20 @@
 
     };
 
+    // Delete a trigger: stop listening for changes and remove it.
+    this.deleteTrigger = function (id) {
+        var method = 'deleteTrigger';
+        var trigger = that.triggers[id];
+        if (trigger) {
+            logger.info(method, 'Stopped cloudant trigger', id, 'listening for changes in database', trigger.dbname);
+            trigger.feed.stop();
+            delete that.triggers[id];
+        } else {
+            logger.info(method, 'trigger', id, 'could not be found in the trigger list.');
+            return false;
+        }
+    };
+
     this.fireTrigger = function (id, change) {
         var method = 'fireTrigger';
 
@@ -287,6 +327,10 @@
         that.postTrigger(dataTrigger, form, uri, auth, that.retryAttempts)
          .then(triggerId => {
              logger.info(method, 'Trigger', triggerId, 'was successfully fired');
+             if (dataTrigger.triggersLeft === 0) {
+                 that.disableTriggerInDB(dataTrigger.id, undefined, 'Automatically disabled after reaching max triggers');
+                 logger.error(method, 'no more triggers left, disabled', dataTrigger.id);
+             }
          }).catch(err => {
              logger.error(method, err);
          });
@@ -310,10 +354,11 @@
                     logger.info(method, dataTrigger.id, 'http post request, STATUS:', response ? response.statusCode : response);
                     if (error || response.statusCode >= 400) {
                         logger.error(method, 'there was an error invoking', dataTrigger.id, response ? response.statusCode : error);
-                        if (!error && [408, 429, 500, 502, 503, 504].indexOf(response.statusCode) === -1) {
-                            //delete dead triggers
-                            that.deleteTriggerFromDB(dataTrigger.id);
-                            reject('Deleted dead trigger ' + dataTrigger.id);
+                        if (!error && that.shouldDisableTrigger(response.statusCode)) {
+                            //disable trigger
+                            var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code when firing the trigger';
+                            that.disableTriggerInDB(dataTrigger.id, response.statusCode, message);
+                            reject('Disabled trigger ' + dataTrigger.id + ' due to status code: ' + response.statusCode);
                         }
                         else {
                             if (retryCount > 0) {
@@ -336,11 +381,6 @@
                             dataTrigger.triggersLeft--;
                         }
                         logger.info(method, 'fired', dataTrigger.id, dataTrigger.triggersLeft, 'triggers left');
-
-                        if (dataTrigger.triggersLeft === 0) {
-                            logger.info(method, 'no more triggers left, deleting', dataTrigger.id);
-                            that.deleteTriggerFromDB(dataTrigger.id);
-                        }
                         resolve(dataTrigger.id);
                     }
                 }
diff --git a/tests/build.gradle b/tests/build.gradle
index 87d3451..698277b 100644
--- a/tests/build.gradle
+++ b/tests/build.gradle
@@ -23,7 +23,7 @@
 
 task testHealth(type: Test) {
     systemProperty 'test.router', 'true'
-    include 'system/packages/**'
+    include 'system/health/**'
 }
 
 dependencies {
diff --git a/tests/src/test/scala/catalog/CloudantUtil.java b/tests/src/test/scala/system/CloudantUtil.java
similarity index 99%
rename from tests/src/test/scala/catalog/CloudantUtil.java
rename to tests/src/test/scala/system/CloudantUtil.java
index 36a5b06..23ef659 100755
--- a/tests/src/test/scala/catalog/CloudantUtil.java
+++ b/tests/src/test/scala/system/CloudantUtil.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog;
+package system;
 
 import com.cloudant.client.api.CloudantClient;
 import com.cloudant.client.api.Database;
diff --git a/tests/src/test/scala/system/packages/CloudantHealthFeedTests.scala b/tests/src/test/scala/system/health/CloudantHealthFeedTests.scala
similarity index 98%
rename from tests/src/test/scala/system/packages/CloudantHealthFeedTests.scala
rename to tests/src/test/scala/system/health/CloudantHealthFeedTests.scala
index f0a818a..48f3a76 100644
--- a/tests/src/test/scala/system/packages/CloudantHealthFeedTests.scala
+++ b/tests/src/test/scala/system/health/CloudantHealthFeedTests.scala
@@ -13,21 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package system.packages
+package system.health
 
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
-
-import catalog.CloudantUtil
 import common.TestHelpers
 import common.Wsk
+import common.WskActorSystem
 import common.WskProps
 import common.WskTestHelpers
 import spray.json.DefaultJsonProtocol.IntJsonFormat
 import spray.json.DefaultJsonProtocol.StringJsonFormat
 import spray.json.pimpAny
-import common.WskActorSystem
+import system.CloudantUtil
 
 /**
  * Tests for Cloudant trigger service
@@ -43,7 +42,7 @@
     val wsk = new Wsk
     val myCloudantCreds = CloudantUtil.Credential.makeFromVCAPFile("cloudantNoSQLDB", this.getClass.getSimpleName)
 
-    behavior of "Cloudant trigger service"
+    behavior of "Cloudant Health trigger service"
 
     it should "bind cloudant package and fire changes trigger using changes feed" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
diff --git a/tests/src/test/scala/catalog/cloudant/CloudantAccountActionsTests.scala b/tests/src/test/scala/system/packages/CloudantAccountActionsTests.scala
similarity index 99%
rename from tests/src/test/scala/catalog/cloudant/CloudantAccountActionsTests.scala
rename to tests/src/test/scala/system/packages/CloudantAccountActionsTests.scala
index 95ff63b..6187f38 100644
--- a/tests/src/test/scala/catalog/cloudant/CloudantAccountActionsTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantAccountActionsTests.scala
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog.cloudant
+package system.packages
 
-import catalog.CloudantUtil
 import common._
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
 import spray.json.DefaultJsonProtocol.StringJsonFormat
 import spray.json.{JsArray, JsNumber, JsString, JsValue, pimpAny}
+import system.CloudantUtil
 import whisk.utils.JsHelpers
 
 import scala.collection.mutable.HashSet
diff --git a/tests/src/test/scala/catalog/cloudant/CloudantBindingTests.scala b/tests/src/test/scala/system/packages/CloudantBindingTests.scala
similarity index 95%
rename from tests/src/test/scala/catalog/cloudant/CloudantBindingTests.scala
rename to tests/src/test/scala/system/packages/CloudantBindingTests.scala
index 175bbb0..c822586 100644
--- a/tests/src/test/scala/catalog/cloudant/CloudantBindingTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantBindingTests.scala
@@ -13,20 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog.cloudant
+package system.packages
 
+import common.{TestHelpers, Wsk, WskProps, WskTestHelpers}
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
-
-import catalog.CloudantUtil
-import common.TestHelpers
-import common.Wsk
-import common.WskProps
-import common.WskTestHelpers
 import spray.json.DefaultJsonProtocol.StringJsonFormat
-import spray.json.JsObject
-import spray.json.pimpAny
+import spray.json.{JsObject, pimpAny}
+import system.CloudantUtil
 
 @RunWith(classOf[JUnitRunner])
 class CloudantBindingTests extends FlatSpec
diff --git a/tests/src/test/scala/catalog/cloudant/CloudantDatabaseActionsTests.scala b/tests/src/test/scala/system/packages/CloudantDatabaseActionsTests.scala
similarity index 98%
rename from tests/src/test/scala/catalog/cloudant/CloudantDatabaseActionsTests.scala
rename to tests/src/test/scala/system/packages/CloudantDatabaseActionsTests.scala
index 69b3875..1e8958d 100644
--- a/tests/src/test/scala/catalog/cloudant/CloudantDatabaseActionsTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantDatabaseActionsTests.scala
@@ -13,19 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog.cloudant
+package system.packages
 
 import java.util.Date
 
-import catalog.CloudantUtil
-import catalog.CloudantUtil._
 import common._
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
-import spray.json.DefaultJsonProtocol.StringJsonFormat
-import spray.json.DefaultJsonProtocol.ByteJsonFormat
+import spray.json.DefaultJsonProtocol.{ByteJsonFormat, StringJsonFormat}
 import spray.json.{JsArray, JsBoolean, JsNumber, JsObject, JsString, pimpAny, pimpString}
+import system.CloudantUtil
 import whisk.utils.JsHelpers
 
 @RunWith(classOf[JUnitRunner])
@@ -412,9 +410,10 @@
                         activation.response.success shouldBe true
                         activation.response.result.get.fields.get("id") shouldBe defined
                         activation.response.result.get.fields.get("rev") shouldBe defined
+                        activation.response.result.get.fields.get("rev") shouldBe defined
                 }
                 //Assert that document does not exist
-                val getResponse = getDocument(credential, response.get("id").getAsString)
+                val getResponse = CloudantUtil.getDocument(credential, response.get("id").getAsString)
                 getResponse.get("error").getAsString shouldBe "not_found"
             }
             finally {
@@ -641,7 +640,7 @@
                                 "dbname" -> credential.dbname.toJson))
                 }
                 //Create test design index doc
-                val indexDesignDoc = CloudantUtil.createDesignFromFile(INDEX_DDOC_PATH).toString
+                val indexDesignDoc = CloudantUtil.createDesignFromFile(CloudantUtil.INDEX_DDOC_PATH).toString
                 val indexDocJSObj = indexDesignDoc.parseJson.asJsObject
 
                 println("Invoking the create-query-index action.")
@@ -717,7 +716,7 @@
                                 "dbname" -> credential.dbname.toJson))
                 }
                 //Create test design index doc
-                val indexDesignDoc = CloudantUtil.createDesignFromFile(INDEX_DDOC_PATH).toString
+                val indexDesignDoc = CloudantUtil.createDesignFromFile(CloudantUtil.INDEX_DDOC_PATH).toString
                 val response = CloudantUtil.createDocument(credential, indexDesignDoc)
                 response.get("ok").getAsString shouldBe "true"
 
@@ -838,7 +837,7 @@
                 response.get("ok").getAsString shouldBe "true"
 
                 //Create test design doc
-                val designDoc = CloudantUtil.createDesignFromFile(VIEW_AND_SEARCH_DDOC_PATH).toString
+                val designDoc = CloudantUtil.createDesignFromFile(CloudantUtil.VIEW_AND_SEARCH_DDOC_PATH).toString
                 val getResponse = CloudantUtil.createDocument(credential, designDoc)
                 getResponse.get("ok").getAsString shouldBe "true"
 
@@ -889,7 +888,7 @@
                 response.get("ok").getAsString shouldBe "true"
 
                 //Create test design doc
-                val designDoc = CloudantUtil.createDesignFromFile(VIEW_AND_SEARCH_DDOC_PATH).toString
+                val designDoc = CloudantUtil.createDesignFromFile(CloudantUtil.VIEW_AND_SEARCH_DDOC_PATH).toString
                 val getResponse = CloudantUtil.createDocument(credential, designDoc)
                 getResponse.get("ok").getAsString shouldBe "true"
 
@@ -936,7 +935,7 @@
                 response.get("ok").getAsString shouldBe "true"
 
                 //Create test design doc
-                val designDoc = CloudantUtil.createDesignFromFile(VIEW_AND_SEARCH_DDOC_PATH).toString
+                val designDoc = CloudantUtil.createDesignFromFile(CloudantUtil.VIEW_AND_SEARCH_DDOC_PATH).toString
                 val getResponse = CloudantUtil.createDocument(credential, designDoc)
                 getResponse.get("ok").getAsString shouldBe "true"
 
@@ -985,7 +984,7 @@
                 response.get("ok").getAsString shouldBe "true"
 
                 //Create test design doc
-                val designDoc = CloudantUtil.createDesignFromFile(VIEW_AND_SEARCH_DDOC_PATH).toString
+                val designDoc = CloudantUtil.createDesignFromFile(CloudantUtil.VIEW_AND_SEARCH_DDOC_PATH).toString
                 val getResponse = CloudantUtil.createDocument(credential, designDoc)
                 getResponse.get("ok").getAsString shouldBe "true"
 
@@ -1041,7 +1040,7 @@
                         val result = activation.response.result.get
                         result.fields.get("ok") shouldBe Some(JsBoolean(true))
                 }
-                val getResponse = getDocument(credential, id)
+                val getResponse = CloudantUtil.getDocument(credential, id)
                 getResponse.get("error").getAsString shouldBe "not_found"
                 getResponse.get("reason").getAsString shouldBe "deleted"
             }
@@ -1114,7 +1113,7 @@
                                 "dbname" -> credential.dbname.toJson))
                 }
                 //Create test index
-                val view = CloudantUtil.createDesignFromFile(VIEW_AND_SEARCH_DDOC_PATH)
+                val view = CloudantUtil.createDesignFromFile(CloudantUtil.VIEW_AND_SEARCH_DDOC_PATH)
                 val response = CloudantUtil.createDocument(credential, view.toString)
                 response.get("ok").getAsString shouldBe "true"
                 val id = response.get("id").getAsString
@@ -1129,7 +1128,7 @@
                         result.fields.get("ok") shouldBe Some(JsBoolean(true))
                 }
                 //Assert that view is deleted
-                val getResponse = getDocument(credential, id)
+                val getResponse = CloudantUtil.getDocument(credential, id)
                 getResponse.get("views").toString shouldBe "{}"
             }
             finally {
@@ -1697,7 +1696,7 @@
                         val result = activation.response.result.get
                         result.fields.get("id") shouldBe Some(JsString(id))
                 }
-                val docResponse = getDocument(credential, id)
+                val docResponse = CloudantUtil.getDocument(credential, id)
                 val updatedAttachment = docResponse.get("_attachments").getAsJsonObject.get(attachmentName).getAsJsonObject
                 getResponse.get("_attachments").getAsJsonObject.has(attachmentName) shouldBe true
                 attachment.get("revpos") should not be updatedAttachment.get("revpos")
@@ -1791,7 +1790,7 @@
                         activation.response.result.get.fields.get("rev") shouldBe defined
                 }
                 //Assert that attachment does not exist in doc
-                val response = getDocument(credential, id).toString
+                val response = CloudantUtil.getDocument(credential, id).toString
                 val docJSObject = response.parseJson.asJsObject
                 docJSObject.fields.get("_attachments") should not be defined
             }
diff --git a/tests/src/test/scala/catalog/cloudant/CloudantFeedTests.scala b/tests/src/test/scala/system/packages/CloudantFeedTests.scala
similarity index 94%
rename from tests/src/test/scala/catalog/cloudant/CloudantFeedTests.scala
rename to tests/src/test/scala/system/packages/CloudantFeedTests.scala
index b497775..e1c6543 100644
--- a/tests/src/test/scala/catalog/cloudant/CloudantFeedTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantFeedTests.scala
@@ -13,22 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog.cloudant
+package system.packages
 
+import common.TestUtils.ANY_ERROR_EXIT
+import common._
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
-
-import catalog.CloudantUtil
-import common.TestHelpers
-import common.Wsk
-import common.WskProps
-import common.WskTestHelpers
-import spray.json.DefaultJsonProtocol.IntJsonFormat
-import spray.json.DefaultJsonProtocol.StringJsonFormat
+import spray.json.DefaultJsonProtocol.{IntJsonFormat, StringJsonFormat}
 import spray.json.pimpAny
-import common.WskActorSystem
-import common.TestUtils.ANY_ERROR_EXIT
+import system.CloudantUtil
 
 /**
  * Tests for Cloudant trigger service
@@ -236,7 +230,7 @@
 
     }
 
-    it should "only invoke as many times as specified" in withAssetCleaner(wskprops) {
+    it should "should disable after reaching max triggers" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             implicit val wskprops = wp // shadow global props and make implicit
         val triggerName = s"dummyCloudantTrigger-${System.currentTimeMillis}"
@@ -267,18 +261,24 @@
                 }
                 feedCreationResult.stdout should include("ok")
 
-                // Create 2 test docs in cloudant and assert that document was inserted successfully
+                // Create test docs in cloudant and assert that document was inserted successfully
                 println("Creating a test doc-1 in the cloudant")
                 val response1 = CloudantUtil.createDocument(myCloudantCreds, "{\"test\":\"test_doc_1\"}")
                 response1.get("ok").getAsString() should be("true")
+
+                println("Checking for activations")
+                val activations = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 5).length
+                println(s"Found activation size (should be exactly 1): $activations")
+                activations should be(1)
+
                 println("Creating a test doc-2 in the cloudant")
                 val response2 = CloudantUtil.createDocument(myCloudantCreds, "{\"test\":\"test_doc_2\"}")
                 response2.get("ok").getAsString() should be("true")
 
-                println("Checking for activations")
-                val activations = wsk.activation.pollFor(N = 2, Some(triggerName)).length
-                println(s"Found activation size (should be exactly 1): $activations")
-                activations should be(1)
+                println("No activations should be created for test_doc_2 since trigger is disabled")
+                val newactivations = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 5).length
+                println(s"Activation size should still be one: $newactivations")
+                newactivations should be(1)
 
             } finally {
                 CloudantUtil.unsetUp(myCloudantCreds)
diff --git a/tests/src/test/scala/catalog/cloudant/CloudantTriggerPersistencyTest.scala b/tests/src/test/scala/system/packages/CloudantTriggerPersistencyTest.scala
similarity index 92%
rename from tests/src/test/scala/catalog/cloudant/CloudantTriggerPersistencyTest.scala
rename to tests/src/test/scala/system/packages/CloudantTriggerPersistencyTest.scala
index 853a3fb..40790c6 100644
--- a/tests/src/test/scala/catalog/cloudant/CloudantTriggerPersistencyTest.scala
+++ b/tests/src/test/scala/system/packages/CloudantTriggerPersistencyTest.scala
@@ -13,22 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package catalog.cloudant
+package system.packages
 
+import common._
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.junit.JUnitRunner
-
-import catalog.CloudantUtil
-import common.TestHelpers
-import common.Wsk
-import common.WskProps
-import common.WhiskProperties
-import common.WskTestHelpers
-import spray.json.DefaultJsonProtocol.IntJsonFormat
-import spray.json.DefaultJsonProtocol.StringJsonFormat
+import spray.json.DefaultJsonProtocol.{IntJsonFormat, StringJsonFormat}
 import spray.json.pimpAny
-import common.WskActorSystem
+import system.CloudantUtil
 
 /**
  * Tests for Cloudant trigger service