<NME> upbat.test.js
<BEF> 'use strict';

require('should');
const fs = require('fs');

var levels = require('../lib/levels');

describe('Uploader Battery', function ( ) {
  var data = {devicestatus: [{mills: Date.now(), uploader: {battery: 20}}]};

  it('display uploader battery status', function (done) {
    var ctx = {
      settings: {}
      , language: require('../lib/language')(fs)
    };
    ctx.language.set('en');
    ctx.levels = levels;
    var sandbox = require('../lib/sandbox')(ctx);
    
    var sbx = sandbox.clientInit(ctx, Date.now(), data);

    sbx.offerProperty = function mockedOfferProperty (name, setter) {
      name.should.equal('upbat');
      var result = setter();
      result.display.should.equal('20%');
      result.status.should.equal('urgent');
      result.min.value.should.equal(20);
      result.min.level.should.equal(25);
      done();
    };

    var upbat = require('../lib/plugins/upbat')(ctx);
    upbat.setProperties(sbx);

  });

  it('set a pill to the uploader battery status', function (done) {
    var ctx = {
      settings: {}
      , pluginBase: {
        updatePillText: function mockedUpdatePillText(plugin, options) {
          options.value.should.equal('20%');
          options.labelClass.should.equal('icon-battery-25');
          options.pillClass.should.equal('urgent');
          done();
        }
      }
      , language: require('../lib/language')(fs)
      , levels: levels
    };
    ctx.language.set('en');

    var sandbox = require('../lib/sandbox')();
    var sbx = sandbox.clientInit(ctx, Date.now(), data);
    var upbat = require('../lib/plugins/upbat')(ctx);
    upbat.setProperties(sbx);
    upbat.updateVisualisation(sbx);

  });

  it('hide the pill if there is no uploader battery status', function (done) {
    var ctx = {
      settings: {}
      , pluginBase: {
        updatePillText: function mockedUpdatePillText (plugin, options) {
          options.hide.should.equal(true);
          done();
        }
      }
      , language: require('../lib/language')(fs)
      , levels: levels
    };
    ctx.language.set('en');

    var sandbox = require('../lib/sandbox')();
    var sbx = sandbox.clientInit(ctx, Date.now(), {});
    var upbat = require('../lib/plugins/upbat')(ctx);
    upbat.setProperties(sbx);
    upbat.updateVisualisation(sbx);
  });

  it('hide the pill if there is uploader battery status is -1', function (done) {
    var ctx = {
    upbat.updateVisualisation(sbx);
  });



});

    var sandbox = require('../lib/sandbox')();
    var sbx = sandbox.clientInit(ctx, Date.now(), data);
    var upbat = require('../lib/plugins/upbat')(ctx);
    upbat.setProperties(sbx);

    upbat.virtAsst.intentHandlers.length.should.equal(2);

    upbat.virtAsst.intentHandlers[0].intentHandler(function next(title, response) {
      title.should.equal('Uploader Battery');
      response.should.equal('Your uploader battery is at 20%');
      
      upbat.virtAsst.intentHandlers[1].intentHandler(function next(title, response) {
        title.should.equal('Uploader Battery');
        response.should.equal('Your uploader battery is at 20%');

        done();
      }, [], sbx);
      
    }, [], sbx);

  });

});

<MSG> refactoring, add alexa tests

<DFF> @@ -84,6 +84,26 @@ describe('Uploader Battery', function ( ) {
     upbat.updateVisualisation(sbx);
   });
 
+  it('should handle alexa requests', function (done) {
 
+    var ctx = {
+      settings: {}
+    };
+
+    var sandbox = require('../lib/sandbox')();
+    var sbx = sandbox.clientInit(ctx, Date.now(), data);
+    var upbat = require('../lib/plugins/upbat')();
+    upbat.setProperties(sbx);
+
+    upbat.alexa.intentHandlers.length.should.equal(1);
+
+    upbat.alexa.intentHandlers[0].intentHandler(function next(title, response) {
+      title.should.equal('Uploader battery');
+      response.should.equal('Your uploader battery is at 25 percent');
+
+      done();
+    }, [], sbx);
+
+  });
 
 });

{'commit': "diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js\nindex 1e566ad7..3eb27cdd 100644\n--- a/lib/plugins/ar2.js\n+++ b/lib/plugins/ar2.js\n@@ -20,42 +20,10 @@ function init (ctx) {\n \n   var translate = ctx.language.translate;\n \n-  var alexaAr2Handler = function(next, slots, sbx) {\n-    if (sbx.properties.ar2.forecast.predicted) {\n-      var forecast = sbx.properties.ar2.forecast.predicted;\n-      var max = forecast[0].mgdl;\n-      var min = forecast[0].mgdl;\n-      var maxForecastMills = forecast[0].mills;\n-      for (var i = 1, len = forecast.length; i < len; i++) {\n-        if (forecast[i].mgdl > max) {\n-          max = forecast[i].mgdl;\n-        }\n-        if (forecast[i].mgdl < min) {\n-          min = forecast[i].mgdl;\n-        }\n-        if (forecast[i].mills > maxForecastMills) {\n-          maxForecastMills = forecast[i].mills;\n-        }\n-      }\n-      var response = 'You are expected to be between ' + min + ' and ' + max + ' over the ' + moment(maxForecastMills).fromNow();\n-      next('AR2 Forecast', response);\n-    } else {\n-      next('AR2 Forecast', 'AR2 plugin does not seem to be enabled');\n-    }\n-  };\n-\n   var ar2 = {\n     name: 'ar2'\n     , label: 'AR2'\n     , pluginType: 'forecast'\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'MetricNow'\n-      , routableSlot:'metric'\n-      , slots:['ar2 forecast', 'forecast']\n-      , intentHandler: alexaAr2Handler\n-      }]\n-    }\n   };\n \n   function buildTitle(prop, sbx) {\n@@ -180,6 +148,39 @@ function init (ctx) {\n     return result.points;\n   };\n \n+  function alexaAr2Handler (next, slots, sbx) {\n+    if (sbx.properties.ar2.forecast.predicted) {\n+      var forecast = sbx.properties.ar2.forecast.predicted;\n+      var max = forecast[0].mgdl;\n+      var min = forecast[0].mgdl;\n+      var maxForecastMills = forecast[0].mills;\n+      for (var i = 1, len = forecast.length; i < len; i++) {\n+        if (forecast[i].mgdl > max) {\n+          max = forecast[i].mgdl;\n+        }\n+        if (forecast[i].mgdl < min) {\n+          min = forecast[i].mgdl;\n+        }\n+        if (forecast[i].mills > maxForecastMills) {\n+          maxForecastMills = forecast[i].mills;\n+        }\n+      }\n+      var response = 'You are expected to be between ' + min + ' and ' + max + ' over the ' + moment(maxForecastMills).fromNow();\n+      next('AR2 Forecast', response);\n+    } else {\n+      next('AR2 Forecast', 'AR2 plugin does not seem to be enabled');\n+    }\n+  }\n+\n+  ar2.alexa = {\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot:'metric'\n+      , slots:['ar2 forecast', 'forecast']\n+      , intentHandler: alexaAr2Handler\n+    }]\n+  };\n+\n   return ar2;\n }\n \ndiff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js\nindex c734669a..981ce678 100644\n--- a/lib/plugins/basalprofile.js\n+++ b/lib/plugins/basalprofile.js\n@@ -6,45 +6,10 @@ function init (ctx) {\n \n   var translate = ctx.language.translate;\n \n-  var alexaCurrentBasalhandler =  function (next, slots, sbx) {\n-      next('Current Basal', basalMessage(slots, sbx));\n-  };\n-\n-  function basalMessage(slots, sbx) {\n-    var basalValue = sbx.data.profile.getTempBasal(Date.now());\n-    var response = 'Unable to determine current basal';\n-    var preamble = '';\n-    if (basalValue.treatment) {\n-      preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' has a ' : 'Your ';\n-      var minutesLeft = moment(basalValue.treatment.endmills).fromNow();\n-      response = preamble + 'temp basal of ' + basalValue.totalbasal + ' units per hour will end ' + minutesLeft;\n-    } else {\n-      preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' ' : 'Your ';\n-      response = preamble + 'current basal is ' + basalValue.totalbasal + ' units per hour';\n-    }\n-    return response;\n-  }\n-\n-  var alexaRollupCurrentBasalHandler = function (slots, sbx, callback) {\n-    callback(null, {results: basalMessage(slots, sbx), priority: 1});\n-  };\n-\n   var basal = {\n     name: 'basal'\n     , label: 'Basal Profile'\n     , pluginType: 'pill-minor'\n-    , alexa : {\n-      rollupHandlers: [{\n-        rollupGroup: 'Status'\n-        , rollupName: 'current basal'\n-        , rollupHandler: alexaRollupCurrentBasalHandler\n-      }],\n-      intentHandlers: [{\n-      intent: 'MetricNow'\n-      , routableSlot:'metric'\n-      , slots:['basal', 'current basal']\n-      , intentHandler: alexaCurrentBasalhandler\n-    }]}\n   };\n \n   basal.setProperties = function setProperties (sbx) {\n@@ -135,6 +100,43 @@ function init (ctx) {\n \n   };\n \n+  function basalMessage(slots, sbx) {\n+    var basalValue = sbx.data.profile.getTempBasal(Date.now());\n+    var response = 'Unable to determine current basal';\n+    var preamble = '';\n+    if (basalValue.treatment) {\n+      preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' has a ' : 'Your ';\n+      var minutesLeft = moment(basalValue.treatment.endmills).fromNow();\n+      response = preamble + 'temp basal of ' + basalValue.totalbasal + ' units per hour will end ' + minutesLeft;\n+    } else {\n+      preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value + ' ' : 'Your ';\n+      response = preamble + 'current basal is ' + basalValue.totalbasal + ' units per hour';\n+    }\n+    return response;\n+  }\n+\n+  function alexaRollupCurrentBasalHandler (slots, sbx, callback) {\n+    callback(null, {results: basalMessage(slots, sbx), priority: 1});\n+  }\n+\n+  function alexaCurrentBasalhandler (next, slots, sbx) {\n+    next('Current Basal', basalMessage(slots, sbx));\n+  }\n+\n+  basal.alexa = {\n+    rollupHandlers: [{\n+      rollupGroup: 'Status'\n+      , rollupName: 'current basal'\n+      , rollupHandler: alexaRollupCurrentBasalHandler\n+    }],\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot:'metric'\n+      , slots:['basal', 'current basal']\n+      , intentHandler: alexaCurrentBasalhandler\n+    }]\n+  };\n+\n   return basal;\n }\n \ndiff --git a/lib/plugins/cob.js b/lib/plugins/cob.js\nindex 4fac6231..f558d11b 100644\n--- a/lib/plugins/cob.js\n+++ b/lib/plugins/cob.js\n@@ -7,28 +7,10 @@ function init(ctx) {\n   var translate = ctx.language.translate;\n   var iob = require('./iob')(ctx);\n \n-  var alexaCOBHandler = function (next, slots, sbx) {\n-    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\\'s', '') + ' has' : 'You have';\n-    var value = 'no';\n-    if (sbx.properties.cob && sbx.properties.cob.cob !== 0) {\n-      value = sbx.properties.cob.cob;\n-    }\n-    var response = preamble + ' ' + value + ' carbohydrates on board';\n-    next('Current COB', response);\n-  };\n-\n   var cob = {\n     name: 'cob'\n     , label: 'Carbs-on-Board'\n     , pluginType: 'pill-minor'\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'MetricNow'\n-        , routableSlot:'metric'\n-        , slots:['cob', 'carbs on board', 'carbohydrates on board']\n-        , intentHandler: alexaCOBHandler\n-      }]\n-    }\n   };\n \n   cob.setProperties = function setProperties(sbx) {\n@@ -190,6 +172,25 @@ function init(ctx) {\n     });\n   };\n \n+  function alexaCOBHandler (next, slots, sbx) {\n+    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\\'s', '') + ' has' : 'You have';\n+    var value = 'no';\n+    if (sbx.properties.cob && sbx.properties.cob.cob !== 0) {\n+      value = sbx.properties.cob.cob;\n+    }\n+    var response = preamble + ' ' + value + ' carbohydrates on board';\n+    next('Current COB', response);\n+  }\n+\n+  cob.alexa = {\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot:'metric'\n+      , slots:['cob', 'carbs on board', 'carbohydrates on board']\n+      , intentHandler: alexaCOBHandler\n+    }]\n+  };\n+\n   return cob;\n \n }\ndiff --git a/lib/plugins/iob.js b/lib/plugins/iob.js\nindex 8714c7be..509c1814 100644\n--- a/lib/plugins/iob.js\n+++ b/lib/plugins/iob.js\n@@ -8,41 +8,10 @@ var _ = require('lodash')\n function init(ctx) {\n   var translate = ctx.language.translate;\n \n-  var alexaIOBIntentHandler = function (callback, slots, sbx) {\n-    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\\'s', '') + ' has ' : 'You have ';\n-    var message = preamble + getIob(sbx) + ' insulin on board';\n-    callback('Current IOB', message);\n-  };\n-\n-  var alexaIOBRollupHandler = function (slots, sbx, callback) {\n-    var message = 'and you have ' + getIob(sbx) + ' insulin on board.';\n-    callback(null, {results: message, priority: 2});\n-  };\n-\n-  function getIob(sbx) {\n-    if (sbx.properties.iob && sbx.properties.iob.iob !== 0) {\n-      return sbx.properties.iob.iob + ' units of';\n-    }\n-    return 'no';\n-  }\n-\n   var iob = {\n     name: 'iob'\n     , label: 'Insulin-on-Board'\n     , pluginType: 'pill-major'\n-    , alexa : {\n-      rollupHandlers: [{\n-        rollupGroup: 'Status'\n-        , rollupName: 'current iob'\n-        , rollupHandler: alexaIOBRollupHandler\n-      }]\n-      , intentHandlers: [{\n-        intent: 'MetricNow'\n-        , routableSlot: 'metric'\n-        , slots: ['iob', 'insulin on board']\n-        , intentHandler: alexaIOBIntentHandler\n-      }]\n-    }\n   };\n \n   iob.RECENCY_THRESHOLD = times.mins(30).msecs;\n@@ -245,6 +214,38 @@ function init(ctx) {\n \n   };\n \n+  function alexaIOBIntentHandler (callback, slots, sbx) {\n+    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\\'s', '') + ' has ' : 'You have ';\n+    var message = preamble + getIob(sbx) + ' insulin on board';\n+    callback('Current IOB', message);\n+  }\n+\n+  function alexaIOBRollupHandler (slots, sbx, callback) {\n+    var message = 'and you have ' + getIob(sbx) + ' insulin on board.';\n+    callback(null, {results: message, priority: 2});\n+  }\n+\n+  function getIob(sbx) {\n+    if (sbx.properties.iob && sbx.properties.iob.iob !== 0) {\n+      return sbx.properties.iob.iob + ' units of';\n+    }\n+    return 'no';\n+  }\n+\n+  iob.alexa = {\n+    rollupHandlers: [{\n+      rollupGroup: 'Status'\n+      , rollupName: 'current iob'\n+      , rollupHandler: alexaIOBRollupHandler\n+    }]\n+    , intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot: 'metric'\n+      , slots: ['iob', 'insulin on board']\n+      , intentHandler: alexaIOBIntentHandler\n+    }]\n+  };\n+\n   return iob;\n \n }\ndiff --git a/lib/plugins/loop.js b/lib/plugins/loop.js\nindex 69fad3b1..0bd75255 100644\n--- a/lib/plugins/loop.js\n+++ b/lib/plugins/loop.js\n@@ -10,60 +10,10 @@ var levels = require('../levels');\n function init(ctx) {\n   var timeago = require('./timeago')(ctx);\n \n-  var alexaForecastHandler = function (next, slots, sbx) {\n-    if (sbx.properties.loop.lastLoop.predicted) {\n-      var forecast = sbx.properties.loop.lastLoop.predicted.values;\n-      var max = forecast[0];\n-      var min = forecast[0];\n-      var maxForecastIndex = Math.min(6, forecast.length);\n-      var startPrediction = moment(sbx.properties.loop.lastLoop.predicted.startDate);\n-      var endPrediction = startPrediction.add(maxForecastIndex * 5, 'minutes');\n-      if (endPrediction.valueOf() < moment()) {\n-        next('Loop Forecast', 'Unable to forecast with the data that is available');\n-      } else {\n-        for (var i = 1, len = forecast.slice(0, maxForecastIndex).length; i < len; i++) {\n-          if (forecast[i] > max) {\n-            max = forecast[i];\n-          }\n-          if (forecast[i] < min) {\n-            min = forecast[i];\n-          }\n-        }\n-        var value = '';\n-        if (min === max) {\n-          value = 'around ' + max;\n-        } else {\n-          value = 'between ' + min + ' and ' + max;\n-        }\n-        var response = 'According to the loop forecast you are expected to be ' + value + ' over the next ' + moment(endPrediction).fromNow(true);\n-        next('Loop Forecast', response);\n-      }\n-    } else {\n-      next('Loop forecast', 'Loop plugin does not seem to be enabled');\n-    }\n-  };\n-\n-  var alexaLastLoopHandler = function(next, slots, sbx) {\n-    console.log(JSON.stringify(sbx.properties.loop.lastLoop));\n-    var response = 'The last successful loop was ' + moment(sbx.properties.loop.lastOkMoment).fromNow();\n-    next('Last loop', response);\n-  };\n-\n   var loop = {\n     name: 'loop'\n     , label: 'Loop'\n     , pluginType: 'pill-status'\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'MetricNow'\n-        , routableSlot: 'metric'\n-        , slots: ['loop forecast', 'forecast']\n-        , intentHandler: alexaForecastHandler\n-      }, {\n-        intent: 'LastLoop'\n-        , intentHandler: alexaLastLoopHandler\n-      }]\n-    }\n   };\n \n   var firstPrefs = true;\n@@ -369,6 +319,58 @@ function init(ctx) {\n     }\n   };\n \n+  function alexaForecastHandler (next, slots, sbx) {\n+    if (sbx.properties.loop.lastLoop.predicted) {\n+      var forecast = sbx.properties.loop.lastLoop.predicted.values;\n+      var max = forecast[0];\n+      var min = forecast[0];\n+      var maxForecastIndex = Math.min(6, forecast.length);\n+\n+      var startPrediction = moment(sbx.properties.loop.lastLoop.predicted.startDate);\n+      var endPrediction = startPrediction.clone().add(maxForecastIndex * 5, 'minutes');\n+      if (endPrediction.valueOf() < sbx.time) {\n+        next('Loop Forecast', 'Unable to forecast with the data that is available');\n+      } else {\n+        for (var i = 1, len = forecast.slice(0, maxForecastIndex).length; i < len; i++) {\n+          if (forecast[i] > max) {\n+            max = forecast[i];\n+          }\n+          if (forecast[i] < min) {\n+            min = forecast[i];\n+          }\n+        }\n+        var value = '';\n+        if (min === max) {\n+          value = 'around ' + max;\n+        } else {\n+          value = 'between ' + min + ' and ' + max;\n+        }\n+        var response = 'According to the loop forecast you are expected to be ' + value + ' over the next ' + moment(endPrediction).from(moment(sbx.time));\n+        next('Loop Forecast', response);\n+      }\n+    } else {\n+      next('Loop forecast', 'Loop plugin does not seem to be enabled');\n+    }\n+  }\n+\n+  function alexaLastLoopHandler(next, slots, sbx) {\n+    console.log(JSON.stringify(sbx.properties.loop.lastLoop));\n+    var response = 'The last successful loop was ' + moment(sbx.properties.loop.lastOkMoment).from(moment(sbx.time));\n+    next('Last loop', response);\n+  }\n+\n+  loop.alexa = {\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot: 'metric'\n+      , slots: ['loop forecast', 'forecast']\n+      , intentHandler: alexaForecastHandler\n+    }, {\n+      intent: 'LastLoop'\n+      , intentHandler: alexaLastLoopHandler\n+    }]\n+  };\n+\n   function statusClass (prop, prefs, sbx) {\n     var level = statusLevel(prop, prefs, sbx);\n     var cls = 'current';\ndiff --git a/lib/plugins/openaps.js b/lib/plugins/openaps.js\nindex 13b060c0..99431611 100644\n--- a/lib/plugins/openaps.js\n+++ b/lib/plugins/openaps.js\n@@ -11,34 +11,10 @@ function init(ctx) {\n   var timeago = require('./timeago')(ctx);\n   var translate = ctx.language.translate;\n \n-  var alexaForecastHandler = function (next, slots, sbx) {\n-    if (sbx.properties.openaps && sbx.properties.openaps.lastEventualBG) {\n-      var response = 'The OpenAPS Eventual BG is ' + sbx.properties.openaps.lastEventualBG;\n-      next('Loop Forecast', response);\n-    }\n-  };\n-\n-  var alexaLastLoopHandler = function(next, slots, sbx) {\n-    console.log(JSON.stringify(sbx.properties.openaps.lastLoopMoment));\n-    var response = 'The last successful loop was ' + moment(sbx.properties.openaps.lastLoopMoment).fromNow();\n-    next('Last loop', response);\n-  };\n-\n   var openaps = {\n     name: 'openaps'\n     , label: 'OpenAPS'\n     , pluginType: 'pill-status'\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'MetricNow'\n-        , routableSlot: 'metric'\n-        , slots: ['openaps forecast', 'forecast']\n-        , intentHandler: alexaForecastHandler\n-      }, {\n-        intent: 'LastLoop'\n-        , intentHandler: alexaLastLoopHandler\n-      }]\n-    }\n   };\n \n   var firstPrefs = true;\n@@ -467,6 +443,31 @@ function init(ctx) {\n     }\n   };\n \n+  function alexaForecastHandler (next, slots, sbx) {\n+    if (sbx.properties.openaps && sbx.properties.openaps.lastEventualBG) {\n+      var response = 'The OpenAPS Eventual BG is ' + sbx.properties.openaps.lastEventualBG;\n+      next('Loop Forecast', response);\n+    }\n+  }\n+\n+  function alexaLastLoopHandler (next, slots, sbx) {\n+    console.log(JSON.stringify(sbx.properties.openaps.lastLoopMoment));\n+    var response = 'The last successful loop was ' + moment(sbx.properties.openaps.lastLoopMoment).from(moment(sbx.time));\n+    next('Last loop', response);\n+  }\n+\n+  openaps.alexa = {\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot: 'metric'\n+      , slots: ['openaps forecast', 'forecast']\n+      , intentHandler: alexaForecastHandler\n+    }, {\n+      intent: 'LastLoop'\n+      , intentHandler: alexaLastLoopHandler\n+    }]\n+  };\n+\n   function statusClass (prop, prefs, sbx) {\n     var level = statusLevel(prop, prefs, sbx);\n     return levels.toStatusClass(level);\ndiff --git a/lib/plugins/pump.js b/lib/plugins/pump.js\nindex 90dadc6e..bf278a57 100644\n--- a/lib/plugins/pump.js\n+++ b/lib/plugins/pump.js\n@@ -12,28 +12,10 @@ function init (ctx) {\n   var timeago = require('./timeago')(ctx);\n   var openaps = require('./openaps')(ctx);\n \n-  var alexaReservoirHandler = function(next, slots, sbx) {\n-    var response = 'You have ' + sbx.properties.pump.pump.reservoir + ' units remaining';\n-    next('Remaining insulin', response);\n-  };\n-  var alexaBatteryHandler = function(next, slots, sbx) {\n-    var response = 'Your battery is at ' + sbx.properties.pump.pump.battery.percent + ' percent';\n-    next('Pump battery', response);\n-  };\n-\n   var pump = {\n     name: 'pump'\n     , label: 'Pump'\n     , pluginType: 'pill-status'\n-    , alexa : {\n-      intentHandlers:[{\n-        intent: 'InsulinRemaining',\n-        intentHandler: alexaReservoirHandler\n-      }, {\n-        intent: 'PumpBattery',\n-        intentHandler: alexaBatteryHandler\n-      }]\n-    }\n   };\n \n   pump.getPrefs = function getPrefs (sbx) {\n@@ -150,6 +132,31 @@ function init (ctx) {\n     });\n   };\n \n+  function alexaReservoirHandler (next, slots, sbx) {\n+    var response = 'You have ' + sbx.properties.pump.pump.reservoir + ' units remaining';\n+    next('Remaining insulin', response);\n+  }\n+\n+  function alexaBatteryHandler (next, slots, sbx) {\n+    var battery = _.get(sbx, 'properties.pump.data.battery');\n+    if (battery) {\n+      var response = 'Your battery is at ' + battery.value + ' ' + battery.unit;\n+      next('Pump battery', response);\n+    } else {\n+      next();\n+    }\n+  }\n+\n+  pump.alexa = {\n+    intentHandlers:[{\n+      intent: 'InsulinRemaining',\n+      intentHandler: alexaReservoirHandler\n+    }, {\n+      intent: 'PumpBattery',\n+      intentHandler: alexaBatteryHandler\n+    }]\n+  };\n+\n   function statusClass (level) {\n     var cls = 'current';\n \n@@ -262,10 +269,10 @@ function init (ctx) {\n     updateStatus(pump, result);\n \n     if (pump.battery && pump.battery.percent) {\n-      result.battery = { value: pump.battery.percent };\n+      result.battery = { value: pump.battery.percent, unit: 'percent' };\n       updateBattery('%', prefs, result);\n     } else if (pump.battery && pump.battery.voltage) {\n-      result.battery = { value: pump.battery.voltage };\n+      result.battery = { value: pump.battery.voltage, unit: 'volts'};\n       updateBattery('v', prefs, result);\n     }\n \ndiff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js\nindex 3bfbab58..a3853f88 100644\n--- a/lib/plugins/rawbg.js\n+++ b/lib/plugins/rawbg.js\n@@ -6,24 +6,11 @@ function init (ctx) {\n \n   var translate = ctx.language.translate;\n \n-  var alexaRawBGHandler = function (next, slots, sbx) {\n-    var response = 'Your raw bg is ' + sbx.properties.rawbg.mgdl;\n-    next('Current Raw BG', response);\n-  };\n-\n   var rawbg = {\n     name: 'rawbg'\n     , label: 'Raw BG'\n     , pluginType: 'bg-status'\n     , pillFlip: true\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'MetricNow'\n-        , routableSlot:'metric'\n-        , slots:['raw bg', 'raw blood glucose']\n-        , intentHandler: alexaRawBGHandler\n-      }]\n-    }\n   };\n \n   rawbg.setProperties = function setProperties (sbx) {\n@@ -109,6 +96,20 @@ function init (ctx) {\n     return display;\n   };\n \n+  function alexaRawBGHandler (next, slots, sbx) {\n+    var response = 'Your raw bg is ' + sbx.properties.rawbg.mgdl;\n+    next('Current Raw BG', response);\n+  }\n+\n+  rawbg.alexa = {\n+    intentHandlers: [{\n+      intent: 'MetricNow'\n+      , routableSlot:'metric'\n+      , slots:['raw bg', 'raw blood glucose']\n+      , intentHandler: alexaRawBGHandler\n+    }]\n+  };\n+\n   return rawbg;\n \n }\ndiff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js\nindex 95adb821..02873bd7 100644\n--- a/lib/plugins/upbat.js\n+++ b/lib/plugins/upbat.js\n@@ -5,22 +5,12 @@ var times = require('../times');\n var levels = require('../levels');\n \n function init() {\n-  var alexaUploaderBatteryHandler = function(next, slots, sbx) {\n-    var response = 'Your uploader battery is at ' + sbx.properties.upbat.level + ' percent';\n-    next('Uploader battery', response);\n-  };\n \n   var upbat = {\n     name: 'upbat'\n     , label: 'Uploader Battery'\n     , pluginType: 'pill-status'\n     , pillFlip: true\n-    , alexa : {\n-      intentHandlers: [{\n-        intent: 'UploaderBattery'\n-        , intentHandler: alexaUploaderBatteryHandler\n-      }]\n-    }\n   };\n \n   upbat.getPrefs = function getPrefs(sbx) {\n@@ -223,6 +213,18 @@ function init() {\n     });\n   };\n \n+  function alexaUploaderBatteryHandler (next, slots, sbx) {\n+    var response = 'Your uploader battery is at ' + sbx.properties.upbat.level + ' percent';\n+    next('Uploader battery', response);\n+  }\n+\n+  upbat.alexa = {\n+    intentHandlers: [{\n+      intent: 'UploaderBattery'\n+      , intentHandler: alexaUploaderBatteryHandler\n+    }]\n+  };\n+\n   return upbat;\n \n }\ndiff --git a/tests/ar2.test.js b/tests/ar2.test.js\nindex 22684532..28ec4f55 100644\n--- a/tests/ar2.test.js\n+++ b/tests/ar2.test.js\n@@ -146,4 +146,17 @@ describe('ar2', function ( ) {\n     done();\n   });\n \n+  it('should handle alexa requests', function (done) {\n+    ctx.ddata.sgvs = [{mgdl: 100, mills: before}, {mgdl: 105, mills: now}];\n+    var sbx = prepareSandbox();\n+\n+    ar2.alexa.intentHandlers.length.should.equal(1);\n+\n+    ar2.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('AR2 Forecast');\n+      response.should.equal('You are expected to be between 109 and 120 over the in 30 minutes');\n+      done();\n+    }, [], sbx);\n+  });\n+\n });\n\\ No newline at end of file\ndiff --git a/tests/basalprofileplugin.test.js b/tests/basalprofileplugin.test.js\nindex 09ed3ab7..78f2f563 100644\n--- a/tests/basalprofileplugin.test.js\n+++ b/tests/basalprofileplugin.test.js\n@@ -1,4 +1,4 @@\n-require('should');\n+var should = require('should');\n \n describe('basalprofile', function ( ) {\n \n@@ -76,5 +76,36 @@ describe('basalprofile', function ( ) {\n \n   });\n \n-  \n+  it('should handle alexa requests', function (done) {\n+    var data = {};\n+\n+    var ctx = {\n+      settings: {}\n+      , pluginBase: { }\n+      , language: require('../lib/language')()\n+    };\n+\n+    var time = new Date('2015-06-21T00:00:00').getTime();\n+\n+\n+    var sbx = sandbox.clientInit(ctx, time, data);\n+    sbx.data.profile = profile;\n+\n+    basal.alexa.intentHandlers.length.should.equal(1);\n+    basal.alexa.rollupHandlers.length.should.equal(1);\n+\n+    basal.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Current Basal');\n+      response.should.equal('Your current basal is 0.1 units per hour');\n+\n+      basal.alexa.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) {\n+        should.not.exist(err);\n+        response.results.should.equal('Your current basal is 0.1 units per hour');\n+        response.priority.should.equal(1);\n+        done();\n+      });\n+\n+    }, [], sbx);\n+  });\n+\n });\n\\ No newline at end of file\ndiff --git a/tests/cob.test.js b/tests/cob.test.js\nindex a82e34a1..dd9d24b0 100644\n--- a/tests/cob.test.js\n+++ b/tests/cob.test.js\n@@ -94,5 +94,27 @@ describe('COB', function ( ) {\n \n   });\n \n+  it('should handle alexa requests', function (done) {\n+    var data = {\n+      treatments: [{\n+        carbs: '8'\n+        , 'mills': Date.now() - 60000 //1m ago\n+      }]\n+      , profile: profile\n+    };\n+\n+    var sandbox = require('../lib/sandbox')();\n+    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n+    cob.setProperties(sbx);\n+\n+    cob.alexa.intentHandlers.length.should.equal(1);\n+\n+    cob.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Current COB');\n+      response.should.equal('You have 8 carbohydrates on board');\n+      done();\n+    }, [], sbx);\n+\n+  });\n \n });\ndiff --git a/tests/iob.test.js b/tests/iob.test.js\nindex 1c5f1274..5a8dea0c 100644\n--- a/tests/iob.test.js\n+++ b/tests/iob.test.js\n@@ -1,7 +1,7 @@\n 'use strict';\n \n-require('should');\n var _ = require('lodash');\n+var should = require('should');\n \n describe('IOB', function() {\n   var ctx = {};\n@@ -208,6 +208,35 @@ describe('IOB', function() {\n       });\n     });\n \n+    it('should handle alexa requests', function (done) {\n+\n+      var sbx = {\n+        properties: {\n+          iob: {\n+            iob: 1.5\n+          }\n+        }\n+      };\n+\n+      iob.alexa.intentHandlers.length.should.equal(1);\n+      iob.alexa.rollupHandlers.length.should.equal(1);\n+\n+      iob.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+        title.should.equal('Current IOB');\n+        response.should.equal('You have 1.5 units of insulin on board');\n+\n+        iob.alexa.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) {\n+          should.not.exist(err);\n+          response.results.should.equal('and you have 1.5 units of insulin on board.');\n+          response.priority.should.equal(2);\n+          done();\n+        });\n+\n+      }, [], sbx);\n+\n+    });\n+\n+\n   });\n \n });\ndiff --git a/tests/loop.test.js b/tests/loop.test.js\nindex ed5668e8..77795b3a 100644\n--- a/tests/loop.test.js\n+++ b/tests/loop.test.js\n@@ -230,7 +230,7 @@ describe('loop', function ( ) {\n \n     ctx.notifications.initRequests();\n \n-    var sbx = sandbox.clientInit(ctx, now.add(2, 'hours').valueOf(), {devicestatus: statuses});\n+    var sbx = sandbox.clientInit(ctx, now.clone().add(2, 'hours').valueOf(), {devicestatus: statuses});\n     sbx.extendedSettings = { 'enableAlerts': 'TRUE' };\n     loop.setProperties(sbx);\n     loop.checkNotifications(sbx);\n@@ -241,4 +241,32 @@ describe('loop', function ( ) {\n     done();\n   });\n \n+  it('should handle alexa requests', function (done) {\n+    var ctx = {\n+      settings: {\n+        units: 'mg/dl'\n+      }\n+      , notifications: require('../lib/notifications')(env, ctx)\n+      , language: require('../lib/language')()\n+    };\n+\n+    var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses});\n+    loop.setProperties(sbx);\n+\n+    loop.alexa.intentHandlers.length.should.equal(2);\n+\n+    loop.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Loop Forecast');\n+      response.should.equal('According to the loop forecast you are expected to be between 147 and 149 over the next in 25 minutes');\n+\n+      loop.alexa.intentHandlers[1].intentHandler(function next(title, response) {\n+        title.should.equal('Last loop');\n+        response.should.equal('The last successful loop was a few seconds ago');\n+        done();\n+      }, [], sbx);\n+\n+    }, [], sbx);\n+\n+  });\n+\n });\ndiff --git a/tests/openaps.test.js b/tests/openaps.test.js\nindex 4e294929..7a106041 100644\n--- a/tests/openaps.test.js\n+++ b/tests/openaps.test.js\n@@ -216,7 +216,7 @@ describe('openaps', function ( ) {\n \n     ctx.notifications.initRequests();\n \n-    var sbx = sandbox.clientInit(ctx, now.add(1, 'hours').valueOf(), {devicestatus: statuses});\n+    var sbx = sandbox.clientInit(ctx, now.clone().add(1, 'hours').valueOf(), {devicestatus: statuses});\n     sbx.extendedSettings = { 'enableAlerts': 'TRUE' };\n     openaps.setProperties(sbx);\n     openaps.checkNotifications(sbx);\n@@ -237,7 +237,7 @@ describe('openaps', function ( ) {\n \n     ctx.notifications.initRequests();\n \n-    var sbx = sandbox.clientInit(ctx, now.add(1, 'hours').valueOf(), {\n+    var sbx = sandbox.clientInit(ctx, now.clone().add(1, 'hours').valueOf(), {\n       devicestatus: statuses\n       , treatments: [{eventType: 'OpenAPS Offline', mills: now.valueOf(), duration: 60}]\n     });\n@@ -250,4 +250,32 @@ describe('openaps', function ( ) {\n     done();\n   });\n \n+  it('should handle alexa requests', function (done) {\n+    var ctx = {\n+      settings: {\n+        units: 'mg/dl'\n+      }\n+      , notifications: require('../lib/notifications')(env, ctx)\n+      , language: require('../lib/language')()\n+    };\n+\n+    var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses});\n+    openaps.setProperties(sbx);\n+\n+    openaps.alexa.intentHandlers.length.should.equal(2);\n+\n+    openaps.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Loop Forecast');\n+      response.should.equal('The OpenAPS Eventual BG is 125');\n+\n+      openaps.alexa.intentHandlers[1].intentHandler(function next(title, response) {\n+        title.should.equal('Last loop');\n+        response.should.equal('The last successful loop was 2 minutes ago');\n+        done();\n+      }, [], sbx);\n+\n+    }, [], sbx);\n+\n+  });\n+\n });\ndiff --git a/tests/pump.test.js b/tests/pump.test.js\nindex 9768df3c..deb2b359 100644\n--- a/tests/pump.test.js\n+++ b/tests/pump.test.js\n@@ -245,4 +245,32 @@ describe('pump', function ( ) {\n     done();\n   });\n \n+  it('should handle alexa requests', function (done) {\n+    var ctx = {\n+      settings: {\n+        units: 'mg/dl'\n+      }\n+      , notifications: require('../lib/notifications')(env, ctx)\n+      , language: require('../lib/language')()\n+    };\n+\n+    var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses});\n+    pump.setProperties(sbx);\n+\n+    pump.alexa.intentHandlers.length.should.equal(2);\n+\n+    pump.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Remaining insulin');\n+      response.should.equal('You have 86.4 units remaining');\n+\n+      pump.alexa.intentHandlers[1].intentHandler(function next(title, response) {\n+        title.should.equal('Pump battery');\n+        response.should.equal('Your battery is at 1.52 volts');\n+        done();\n+      }, [], sbx);\n+\n+    }, [], sbx);\n+\n+  });\n+\n });\ndiff --git a/tests/rawbg.test.js b/tests/rawbg.test.js\nindex 59aa3a2b..4697bea9 100644\n--- a/tests/rawbg.test.js\n+++ b/tests/rawbg.test.js\n@@ -7,7 +7,6 @@ describe('Raw BG', function ( ) {\n     settings: {}\n     , language: require('../lib/language')()\n   });\n-  var sandbox = require('../lib/sandbox')();\n \n   var now = Date.now();\n   var data = {\n@@ -22,6 +21,7 @@ describe('Raw BG', function ( ) {\n   };\n \n   it('should calculate Raw BG', function (done) {\n+    var sandbox = require('../lib/sandbox')();\n     var sbx = sandbox.clientInit(ctx, Date.now(), data);\n \n     sbx.offerProperty = function mockedOfferProperty (name, setter) {\n@@ -36,5 +36,22 @@ describe('Raw BG', function ( ) {\n \n   });\n \n+  it('should handle alexa requests', function (done) {\n+\n+    var sandbox = require('../lib/sandbox')();\n+    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n+\n+    rawbg.setProperties(sbx);\n+\n+    rawbg.alexa.intentHandlers.length.should.equal(1);\n+\n+    rawbg.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Current Raw BG');\n+      response.should.equal('Your raw bg is 113');\n+\n+      done();\n+    }, [], sbx);\n+\n+  });\n \n });\ndiff --git a/tests/upbat.test.js b/tests/upbat.test.js\nindex 786252c3..730ccad4 100644\n--- a/tests/upbat.test.js\n+++ b/tests/upbat.test.js\n@@ -84,6 +84,26 @@ describe('Uploader Battery', function ( ) {\n     upbat.updateVisualisation(sbx);\n   });\n \n+  it('should handle alexa requests', function (done) {\n \n+    var ctx = {\n+      settings: {}\n+    };\n+\n+    var sandbox = require('../lib/sandbox')();\n+    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n+    var upbat = require('../lib/plugins/upbat')();\n+    upbat.setProperties(sbx);\n+\n+    upbat.alexa.intentHandlers.length.should.equal(1);\n+\n+    upbat.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Uploader battery');\n+      response.should.equal('Your uploader battery is at 25 percent');\n+\n+      done();\n+    }, [], sbx);\n+\n+  });\n \n });\n", 'commit_subject': 'refactoring, add alexa tests', 'repo_name': 'nightscout/cgm-remote-monitor', 'committer': {'name': 'Jason Calabrese', 'email': '68c46a606457643eab92053c1c05574abb26f861@cbrese.com', 'time_sec': '1478984121', 'tz_offset': '-480', 'date': {'seconds': '1478984121'}}, 'commit_tree': '4cd89bfed5f66c82736b9864ad2f4246b819bc2a', 'commit_message': 'refactoring, add alexa tests\n', 'language_name': 'HTML', 'license': 'agpl-3.0', 'hunks': "@@ -84,6 +84,26 @@ describe('Uploader Battery', function ( ) {\n     upbat.updateVisualisation(sbx);\n   });\n \n+  it('should handle alexa requests', function (done) {\n \n+    var ctx = {\n+      settings: {}\n+    };\n+\n+    var sandbox = require('../lib/sandbox')();\n+    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n+    var upbat = require('../lib/plugins/upbat')();\n+    upbat.setProperties(sbx);\n+\n+    upbat.alexa.intentHandlers.length.should.equal(1);\n+\n+    upbat.alexa.intentHandlers[0].intentHandler(function next(title, response) {\n+      title.should.equal('Uploader battery');\n+      response.should.equal('Your uploader battery is at 25 percent');\n+\n+      done();\n+    }, [], sbx);\n+\n+  });\n \n });\n", 'addition_count': 20, 'deletion_count': 0, 'src_file': 'a/tests/upbat.test.js', 'tgt_file': 'b/tests/upbat.test.js', 'file_extension': '.js', 'before_file': "'use strict';\n\nrequire('should');\nconst fs = require('fs');\n\nvar levels = require('../lib/levels');\n\ndescribe('Uploader Battery', function ( ) {\n  var data = {devicestatus: [{mills: Date.now(), uploader: {battery: 20}}]};\n\n  it('display uploader battery status', function (done) {\n    var ctx = {\n      settings: {}\n      , language: require('../lib/language')(fs)\n    };\n    ctx.language.set('en');\n    ctx.levels = levels;\n    var sandbox = require('../lib/sandbox')(ctx);\n    \n    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n\n    sbx.offerProperty = function mockedOfferProperty (name, setter) {\n      name.should.equal('upbat');\n      var result = setter();\n      result.display.should.equal('20%');\n      result.status.should.equal('urgent');\n      result.min.value.should.equal(20);\n      result.min.level.should.equal(25);\n      done();\n    };\n\n    var upbat = require('../lib/plugins/upbat')(ctx);\n    upbat.setProperties(sbx);\n\n  });\n\n  it('set a pill to the uploader battery status', function (done) {\n    var ctx = {\n      settings: {}\n      , pluginBase: {\n        updatePillText: function mockedUpdatePillText(plugin, options) {\n          options.value.should.equal('20%');\n          options.labelClass.should.equal('icon-battery-25');\n          options.pillClass.should.equal('urgent');\n          done();\n        }\n      }\n      , language: require('../lib/language')(fs)\n      , levels: levels\n    };\n    ctx.language.set('en');\n\n    var sandbox = require('../lib/sandbox')();\n    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n    var upbat = require('../lib/plugins/upbat')(ctx);\n    upbat.setProperties(sbx);\n    upbat.updateVisualisation(sbx);\n\n  });\n\n  it('hide the pill if there is no uploader battery status', function (done) {\n    var ctx = {\n      settings: {}\n      , pluginBase: {\n        updatePillText: function mockedUpdatePillText (plugin, options) {\n          options.hide.should.equal(true);\n          done();\n        }\n      }\n      , language: require('../lib/language')(fs)\n      , levels: levels\n    };\n    ctx.language.set('en');\n\n    var sandbox = require('../lib/sandbox')();\n    var sbx = sandbox.clientInit(ctx, Date.now(), {});\n    var upbat = require('../lib/plugins/upbat')(ctx);\n    upbat.setProperties(sbx);\n    upbat.updateVisualisation(sbx);\n  });\n\n  it('hide the pill if there is uploader battery status is -1', function (done) {\n    var ctx = {\n    upbat.updateVisualisation(sbx);\n  });\n\n\n\n});\n\n    var sandbox = require('../lib/sandbox')();\n    var sbx = sandbox.clientInit(ctx, Date.now(), data);\n    var upbat = require('../lib/plugins/upbat')(ctx);\n    upbat.setProperties(sbx);\n\n    upbat.virtAsst.intentHandlers.length.should.equal(2);\n\n    upbat.virtAsst.intentHandlers[0].intentHandler(function next(title, response) {\n      title.should.equal('Uploader Battery');\n      response.should.equal('Your uploader battery is at 20%');\n      \n      upbat.virtAsst.intentHandlers[1].intentHandler(function next(title, response) {\n        title.should.equal('Uploader Battery');\n        response.should.equal('Your uploader battery is at 20%');\n\n        done();\n      }, [], sbx);\n      \n    }, [], sbx);\n\n  });\n\n});\n", 'message': 'refactoring, add alexa tests\n', 'author_name': 'Jason Calabrese', 'lang': 'test'}