Merge branch 'master' into master
diff --git a/.gitignore b/.gitignore
index 3c0ac2f..23d4050 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,5 @@
 
 .vscode
 .idea
-.DS_Store
\ No newline at end of file
+.DS_Store
+env.sh
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e49a3ea
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+- '6.3.1'
+
diff --git a/README.md b/README.md
index 9311c2f..36f5d8b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 #Plugins
 
+Build Status [![Build Status](https://travis-ci.org/apigee/microgateway-plugins.svg?branch=accumulate-request-fixes)](https://travis-ci.org/apigee/microgateway-plugins)
+
 ##Overview
 This repo contains plugins for the [microgateway-core project](https://github.com/apigee/microgateway-core).  
 
@@ -55,4 +57,4 @@
 
 ```javascript 
 next([err])
-```
\ No newline at end of file
+```
diff --git a/accumulate-request/index.js b/accumulate-request/index.js
index b1e7643..d162087 100644
--- a/accumulate-request/index.js
+++ b/accumulate-request/index.js
@@ -14,7 +14,6 @@
  * should only be used when it is known that request/response bodies are small.
  */
 module.exports.init = function(config, logger, stats) {
-
   function accumulate(req, data) {
     if (!req._chunks) req._chunks = [];
     req._chunks.push(data);
@@ -29,11 +28,13 @@
 
     onend_request: function(req, res, data, next) {
       if (data && data.length > 0) accumulate(req, data);
-      var content = Buffer.concat(req._chunks);
+      var content = null;
+      if (req._chunks && req._chunks.length) {
+        content = Buffer.concat(req._chunks);
+      }
       delete req._chunks;
       next(null, content);
     }
-
   };
 
 }
diff --git a/accumulate-response/index.js b/accumulate-response/index.js
index 8be60af..1ff26a2 100644
--- a/accumulate-response/index.js
+++ b/accumulate-response/index.js
@@ -29,7 +29,10 @@
 
     onend_response: function(req, res, data, next) {
       if (data && data.length > 0) accumulate(res, data);
-      var content = Buffer.concat(res._chunks);
+      var content = null;
+      if(res._chunks && res._chunks.length) {
+        content = Buffer.concat(res._chunks);
+      }
       delete res._chunks;
       next(null, content);
     }
diff --git a/analytics/index.js b/analytics/index.js
index 23c57c7..175ca09 100644
--- a/analytics/index.js
+++ b/analytics/index.js
@@ -9,6 +9,15 @@
       record.apiproxy = res.proxy.name;
       record.apiproxy_revision = res.proxy.revision;
     }
+
+    if(config.mask_request_uri) {
+      record.request_uri = config.mask_request_uri;  
+    }
+
+    if(config.mask_request_path) {
+      record.request_path = config.mask_request_path;
+    }
+    
     cb(null, record);
   };
 
diff --git a/cache/index.js b/cache/index.js
deleted file mode 100644
index e7c08b0..0000000
--- a/cache/index.js
+++ /dev/null
@@ -1,67 +0,0 @@
-'use strict';
-
-var cache = require('volos-cache-memory');
-var async = require('async');
-var debug = require('debug')('gateway:cache');
-
-var getKey = function (req) {
-    return req.token.application_name;
-}
-var caches = [];
-
-var middleware = function (req, res, next) {
-    if (!req.token
-        || !req.token.api_product_list
-        || !req.token.api_product_list.length
-    ) {
-        return next();
-    }
-
-    debug('cache checking products', req.token.api_product_list);
-
-    req.originalUrl = req.originalUrl || req.url; // emulate connect
-
-    var found = false;
-    // this is arbitrary, but not sure there's a better way?
-    async.eachSeries(req.token.api_product_list,
-        function (productName, cb) {
-            if(!found) {
-                var cache = caches[productName];
-                executeDefaultCache(cache, req, res, cb);
-                found = true;
-            }else{
-                cb();
-            }
-        },
-        function (err) {
-            next(err);
-        }
-    );
-}
-
-var executeDefaultCache = function (cache, req, res, cb) {
-    cb();
-};
-
-module.exports.init = function (config, logger, stats) {
-    var options = {
-        key: getKey
-    };
-    Object.keys(config).forEach(function (productName) {
-        var product = config[productName];
-        if (!product.ttl) {
-            debug('could not load cache for ' + productName + ' because ttl is missing', product)
-            return; // skip non-cache config
-        }
-        var cacheInstance = cache.create(config[productName]);
-        caches[productName] = cacheInstance;
-        debug('created cache for', productName);
-        debug('caches are ', caches)
-    });
-
-    return {
-        onrequest: function (req, res, next) {
-            middleware(req, res, next);
-        }
-    };
-};
\ No newline at end of file
diff --git a/cache/package.json b/cache/package.json
deleted file mode 100644
index 7516753..0000000
--- a/cache/package.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "name": "edgemicro-plugin-cache",
-  "version": "1.0.0",
-  "description": "caching plugin for the gateway",
-  "main": "index.js",
-  "scripts": {
-    "test": "mocha tests"
-  },
-  "author": "sfeldman@apigee.com",
-  "dependencies": {
-    "async": "^1.5.2",
-    "debug": "^2.2.0",
-    "volos-cache-memory": "^0.10.0"
-  },
-  "devDependencies": {
-    "mocha": "^2.4.5",
-    "should": "^8.2.1"
-  }
-}
diff --git a/cache/tests/testIndex.js b/cache/tests/testIndex.js
deleted file mode 100644
index a55c654..0000000
--- a/cache/tests/testIndex.js
+++ /dev/null
@@ -1,12 +0,0 @@
-var should = require('should');
-var assert = require('assert');
-var plugin = require('../index');
-describe('construct',function(){
-   it('should work',function(done){
-       var req = plugin.init();
-       req.onrequest(null,null,function(err){
-           should(err).be.null
-           done();
-       })
-   }) ;
-});
\ No newline at end of file
diff --git a/index.js b/index.js
index 01f90dd..fd4f20a 100644
--- a/index.js
+++ b/index.js
@@ -4,7 +4,6 @@
   'accumulate-request':require('./accumulate-request'),
   'accumulate-response':require('./accumulate-response'),
   analytics:require('./analytics'),
-  cache:require('./cache'),
   'header-uppercase':require('./header-uppercase'),
   oauth:require('./oauth'),
   quota:require('./quota'),
@@ -13,4 +12,6 @@
   'transform-uppercase':require('./transform-uppercase'),
   accesscontrol:require('./accesscontrol'),
   eurekaclient:require('./eurekaclient')
+  json2xml: require('./json2xml'),
+  healthcheck: require('./healthcheck')
 }
diff --git a/json2xml/index.js b/json2xml/index.js
index 2b78e72..8548727 100644
--- a/json2xml/index.js
+++ b/json2xml/index.js
@@ -74,14 +74,14 @@
 
 			//if plugin is disabled don't process headers.
 			if (!disable) {
-				if (method === "get" && acceptType === "application/xml") {
+				if (method === "get" && acceptType.indexOf("application/xml") > -1) {
 					responseXML = true;
-				} else if (method === "get" && acceptType === "application/json") {
+				} else if (method === "get" && acceptType.indexOf("application/json") > -1) {
 					responseJSON = true;
-				} else if (method !== "get" && contentType === "application/json") {
+				} else if (method !== "get" && contentType.indexOf("application/json") > -1) {
 					requestJSON = true;
 					responseJSON = true;
-				} else if (method !== "get" && contentType === "application/xml") {
+				} else if (method !== "get" && contentType.indexOf("application/xml") > -1) {
 					requestXML = true;
 					responseXML = true;
 				}
@@ -156,9 +156,9 @@
 			if (data && data.length > 0 && disable == false) accumulateResponse(res, data);
 
 			var contentType = res.getHeader('content-type');
-			if (contentType === "application/xml") {
+			if (contentType.indexOf("application/xml") > -1) {
 				responseIsXML = true;
-			} else if (contentType === "application/json") {
+			} else if (contentType.indexOf("application/json") > -1) {
 				responseIsJSON = true;
 			}
 
@@ -191,4 +191,4 @@
 			}
 		}		
 	};
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 09aa659..a068b50 100644
--- a/package.json
+++ b/package.json
@@ -1,22 +1,25 @@
 {
   "name": "microgateway-plugins",
-  "version": "2.0.4",
+  "version": "2.3.5",
   "description": "Plugins for Apige Edge Microgateway",
   "main": "index.js",
   "scripts": {
-    "test": "bin/test"
+    "test": "./node_modules/.bin/mocha -R spec"
   },
   "dependencies": {
     "async": "^1.4.2",
     "debug": "^2.2.0",
-    "volos-spikearrest-memory": "^0.10.0",
-    "volos-quota-memory": "^0.11.0",
-    "volos-quota-apigee": "^0.13.0",
+    "js2xmlparser": "^2.0.2",
     "jsonwebtoken": "^5.0.1",
     "lodash": "^3.10.1",
     "request": "^2.65.0",
+    "toobusy-js": "^0.5.1",
+    "volos-analytics-apigee": "^0.4.0",
     "volos-cache-memory": "^0.10.0",
-    "volos-analytics-apigee": "^0.4.0"
+    "volos-quota-apigee": "^0.13.0",
+    "volos-quota-memory": "^0.11.0",
+    "volos-spikearrest-memory": "^0.10.0",
+    "xml2js": "^0.4.17"
   },
   "devDependencies": {
     "js-yaml": "^3.4.2",
diff --git a/quota-memory/index.js b/quota-memory/index.js
index 6d0579d..337bf6e 100644
--- a/quota-memory/index.js
+++ b/quota-memory/index.js
@@ -5,7 +5,6 @@
 var debug = require('debug')('gateway:quota');
 
 module.exports.init = function(config, logger, stats) {
-
   var quotas = {}; // productName -> connectMiddleware
 
   var options = {
@@ -14,7 +13,10 @@
 
   Object.keys(config).forEach(function(productName) {
     var product = config[productName];
-    if (!product.uri && !product.key && !product.secret && !product.allow && !product.interval) return; // skip non-quota config
+    if (!product.uri && !product.key && !product.secret && !product.allow && !product.interval) {
+      return; // skip non-quota config
+    }
+
     var quota = Quota.create(config[productName]);
     quotas[productName] = quota.connectMiddleware().apply(options);
     debug('created quota for', productName);
diff --git a/quota/index.js b/quota/index.js
index 0830ecf..7e377fb 100644
--- a/quota/index.js
+++ b/quota/index.js
@@ -9,12 +9,19 @@
   var quotas = {}; // productName -> connectMiddleware
 
   var options = {
-    key: function(req) { return req.token.application_name; }
+    key: function(req) { 
+           return req.token.application_name; 
+    }
   };
 
   Object.keys(config).forEach(function(productName) {
     var product = config[productName];
-    if (!product.uri && !product.key && !product.secret && !product.allow && !product.interval) return; // skip non-quota config
+    if (!product.uri && !product.key && !product.secret && !product.allow && !product.interval) {
+      // skip non-quota config
+      console.log('Not enough info for configuring quota');
+      return;
+    }     
+
     var quota = Quota.create(config[productName]);
     quotas[productName] = quota.connectMiddleware().apply(options);
     debug('created quota for', productName);
diff --git a/test/accumulate-request-test.js b/test/accumulate-request-test.js
new file mode 100644
index 0000000..1983c25
--- /dev/null
+++ b/test/accumulate-request-test.js
@@ -0,0 +1,107 @@
+const accumulateRequest = require('../accumulate-request/index');
+const assert = require('assert');
+
+describe('accumulate request plugin', () => {
+  var plugin = null;
+
+  beforeEach(() => {
+    var config = {};
+    var logger = {};
+    var stats = {};
+
+    plugin = accumulateRequest.init.apply(null, [config, logger, stats]);
+  });
+
+  it('exposes an ondata_request handler', () => {
+    assert.ok(plugin.ondata_request);
+  });
+
+  it('exposes an onend_request handler', () => {
+    assert.ok(plugin.onend_request);
+  });
+
+  it('calls back with two null function arguments in the ondata_request handler', (done) => {
+    var cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      done();
+    }
+
+
+    plugin.ondata_request.apply(null, [{}, {}, Buffer.alloc(5, 'a'), cb]);
+  });
+
+  it('will collect all buffers provided to ondata_request handler, concatenate them, and return them as a single buffer', (done) => {
+    var desiredResult = 'aaaaaaaaaaaaaaa';
+    
+    var ondata_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(req._chunks);
+    }
+
+    var onend_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result.toString(), desiredResult); 
+      done();
+    } 
+
+    var req = {};
+
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    
+    plugin.onend_request.apply(null, [req, {}, null, onend_cb]);  
+  });
+
+  it('will append data included in the end call to the buffer', (done) => {
+    var desiredResult = 'aaaaaaaaaaaaaaaaaaaa';
+    
+    var ondata_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(req._chunks);
+    }
+
+    var onend_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result.toString(), desiredResult); 
+      done();
+    } 
+
+    var req = {};
+
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), ondata_cb]);
+    
+    plugin.onend_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), onend_cb]);  
+  });
+
+  it('will create a req._chunks object on the request object', (done) => {
+    var req = {};
+    var cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(req._chunks);
+      assert.equal(req._chunks.toString(), 'aaaaa');
+      done();
+    }
+
+    plugin.ondata_request.apply(null, [req, {}, Buffer.alloc(5, 'a'), cb]);
+  });
+
+  it('will call the callback with null if no data events are provided.', (done) => {
+    var onend_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null); 
+      done();
+    } 
+
+    var req = {};
+
+    plugin.onend_request.apply(null, [req, {}, null, onend_cb]);  
+
+  });
+})
diff --git a/test/accumulate-response-test.js b/test/accumulate-response-test.js
new file mode 100644
index 0000000..0a13840
--- /dev/null
+++ b/test/accumulate-response-test.js
@@ -0,0 +1,107 @@
+const accumulateResponse = require('../accumulate-response/index');
+const assert = require('assert');
+
+describe('accumulate response plugin', () => {
+  var plugin = null;
+
+  beforeEach(() => {
+    var config = {};
+    var logger = {};
+    var stats = {};
+
+    plugin = accumulateResponse.init.apply(null, [config, logger, stats]);
+  });
+
+  it('exposes an ondata_response handler', () => {
+    assert.ok(plugin.ondata_response);
+  });
+
+  it('exposes an onend_response handler', () => {
+    assert.ok(plugin.onend_response);
+  });
+
+  it('calls back with two null function arguments in the ondata_response handler', (done) => {
+    var cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      done();
+    }
+
+
+    plugin.ondata_response.apply(null, [{}, {}, Buffer.alloc(5, 'a'), cb]);
+  });
+
+  it('will collect all buffers provided to ondata_response handler, concatenate them, and return them as a single buffer', (done) => {
+    var desiredResult = 'aaaaaaaaaaaaaaa';
+    
+    var ondata_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(res._chunks);
+    }
+
+    var onend_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result.toString(), desiredResult); 
+      done();
+    } 
+
+    var res = {};
+
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    
+    plugin.onend_response.apply(null, [{}, res, null, onend_cb]);  
+  });
+
+  it('will append data included in the end call to the buffer', (done) => {
+    var desiredResult = 'aaaaaaaaaaaaaaaaaaaa';
+    
+    var ondata_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(res._chunks);
+    }
+
+    var onend_cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result.toString(), desiredResult); 
+      done();
+    } 
+
+    var res = {};
+
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), ondata_cb]);
+    
+    plugin.onend_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), onend_cb]);  
+  });
+
+  it('will create a res._chunks object on the request object', (done) => {
+    var res = {};
+    var cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      assert.ok(res._chunks);
+      assert.equal(res._chunks.toString(), 'aaaaa');
+      done();
+    }
+
+    plugin.ondata_response.apply(null, [{}, res, Buffer.alloc(5, 'a'), cb]);
+  });
+
+  it('will callback with null if no response body is present in targetResponse', (done) => {
+    var res = {};
+    var cb = (err, result) => {
+      assert.equal(err, null);
+      assert.equal(result, null);
+      done();
+    }
+
+    plugin.onend_response.apply(null, [{}, res, null, cb]);
+
+  });
+
+})
diff --git a/test/quota-test.js b/test/quota-test.js
new file mode 100644
index 0000000..e8a2a31
--- /dev/null
+++ b/test/quota-test.js
@@ -0,0 +1,93 @@
+const quota = require('../quota/index');
+const assert = require('assert');
+
+var exampleConfig = { 
+  EdgeMicroTestProduct: {
+    allow: process.env.QUOTA_ALLOW,
+    interval: Number(process.env.QUOTA_INTERVAL),
+    timeUnit: process.env.QUOTA_TIMEUNIT,
+    bufferSize: process.env.QUOTA_BUFFERSIZE,
+    uri: process.env.QUOTA_URI,
+    key: process.env.QUOTA_KEY,
+    secret: process.env.QUOTA_SECRET 
+  }
+}
+
+describe('quota plugin', () => {
+  var plugin = null;
+  
+  beforeEach(() => {
+    var logger = {};
+    var stats = {};
+
+    plugin = quota.init.apply(null, [exampleConfig, logger, stats]);
+
+  });
+ 
+  it('exposes an onrequest handler', () => {
+    assert.ok(plugin.onrequest);
+  });
+
+  it('will quota limit after 3 API calls', (done) => {
+    var count = 0;
+    var onrequest_cb = (err) => {
+      count++;
+      if(count == 4) {
+        assert.equal(count, 4);
+        assert.equal(err.message, 'exceeded quota');
+        done();
+      } 
+    };
+
+    var req = {
+      token: {
+        application_name: '0e7762f4-ea67-4cc1-ae4a-21598c35b18f',
+        api_product_list: ['EdgeMicroTestProduct']       
+      }
+    }
+
+    var res = {
+      headers: {},
+      setHeader: (key, val) => {
+        res.headers[key] = val;
+      }
+    }
+
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+  });
+
+  it('will not quota limit before 3 API calls', (done) => {
+    var count = 0;
+    var onrequest_cb = (err) => {
+      count++;
+      if(count == 3) {
+        assert.equal(count, 3);
+        assert.ok(!(err instanceof Error));
+        done();
+      } 
+    };
+
+    var req = {
+      token: {
+        application_name: '0e7762f4-ea67-4cc1-ae4a-21598c35b18f',
+        api_product_list: ['EdgeMicroTestProduct']       
+      }
+    }
+
+    var res = {
+      headers: {},
+      setHeader: (key, val) => {
+        res.headers[key] = val;
+      }
+    }
+
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+  });
+});
+
+
diff --git a/test/spike-arrest-test.js b/test/spike-arrest-test.js
new file mode 100644
index 0000000..409eb77
--- /dev/null
+++ b/test/spike-arrest-test.js
@@ -0,0 +1,48 @@
+const spikeArrest = require('../spikearrest/index');
+const assert = require('assert');
+
+describe('spike arrest plugin', () => {
+  var plugin = null;
+
+  beforeEach(() => {
+    var config = {
+      timeUnit: 'minute',
+      bufferSize: 0,
+      allow: 1
+    };
+    var logger = {};
+    var stats = {};
+
+    plugin = spikeArrest.init.apply(null, [config, logger, stats]);
+  });
+
+  it('exposes an onrequest handler', () => {
+    assert.ok(plugin.onrequest);
+  });
+
+  it('will accept a request', (done) => {
+    var onrequest_cb = () => {
+      done();
+    };
+
+    var req = {};
+    var res = {};
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+  });
+
+  it('will reject a requests that come in too fast', (done) => {
+    var onrequest_cb = () => {
+    };
+    
+    var onrequest_cb_second = (req, res, next) => {
+      assert.ok(req instanceof Error);
+      assert.equal(req.message, 'SpikeArrest engaged');
+      done();
+    };
+
+    var req = {};
+    var res = {};
+    plugin.onrequest.apply(null, [req, res, onrequest_cb]);
+    plugin.onrequest.apply(null, [req, res, onrequest_cb_second]);
+  });
+});