.then(function() {
              assert.equal(serviceCalls.length, 1);

              var call = serviceCalls[0];
              assert.equal(call.url, BUGSNAG_URI);
              assert.deepEqual(call.body, { apiKey: '1234' });
            });
    it('passes through config options', function () {
      var plugin = subject.createDeployPlugin({
        name: 'redis'
      });

      var redisLib = new FakeRedis();

      var context = {
        ui: mockUi,
        project: stubProject,
        config: {
          redis: {
            host: 'somehost',
            port: 1234,
            database: 4
          }
        },
        _redisLib: redisLib
      };
      plugin.beforeHook(context);
      plugin.configure(context);
      plugin.readConfig('redisDeployClient');

      assert.equal(redisLib.createdClient.options.host, 'somehost');
      assert.equal(redisLib.createdClient.options.port, 1234);
      assert.equal(redisLib.createdClient.options.database, 4);
    });
      it('uses the config data if it already exists', function() {
        var plugin = subject.createDeployPlugin({
          name: 'tag-git'
        });

        var config = {
          deployTag: 'some-tag',
          revisionKey: '12345'
        };
        var context = {
          ui: mockUi,
          project: stubProject,
          config: {
            "tag-git": config
          },
          commandOptions: {},
          revisionData: {
            revisionKey: 'something-else'
          }
        };

        plugin.beforeHook(context);
        plugin.configure(context);
        assert.equal(plugin.readConfig('revisionKey'), '12345');
        assert.equal(plugin.readConfig('deployTag'), 'some-tag');
      });
          .then(function() {
            assert.equal(serviceCalls.length, 1);

            var call = serviceCalls[0];

            assert.equal(call.url, CUSTOM_URI);
            assert.deepEqual(call.body, { deployer: 'levelbossmike' });
          });
 .then(function(data) {
   assert.equal(data.sha, '41d41f081b45ad50935c08b1203220737d9739b4');
   assert.equal(data.email, '*****@*****.**');
   assert.equal(data.name, 'Alisdair McDiarmid');
   assert.isNotNull(data.timestamp);
   assert.equal(data.branch, 'master');
   assert.equal(data.tag, '2.3.4');
 });
  it('implements the correct deployment hooks', function() {
    var plugin = subject.createDeployPlugin({
      name: 'test-plugin'
    });

    assert.equal(typeof plugin.configure, 'function');
    assert.equal(typeof plugin.prepare, 'function');
    assert.equal(typeof plugin.upload, 'function');
    assert.equal(typeof plugin.didDeploy, 'function');
  });
        .then(function() {
          assert.equal(mockUi.messages.length, 2);

          var messages = mockUi.messages.reduce(function(previous, current) {
            if (/- ✔  js-app\/app\.[js|css]/.test(current)) {
              previous.push(current);
            }

            return previous;
          }, []);

          assert.equal(messages.length, 2);
        });
        .then(function() {
          assert.equal(mockUi.messages.length, 2);

          var messages = mockUi.messages.reduce(function(previous, current) {
            if (/- uploaded 2 files ok/.test(current)) {
              previous.push(current);
            }

            return previous;
          }, []);

          assert.equal(messages.length, 1);
        });
      it('uses the context value if it exists and commandOptions doesn\'t', function() {
        var plugin = subject.createDeployPlugin({
          name: 'redis'
        });

        var config = {
          host: 'somehost',
          port: 1234
        };
        var context = {
          ui: mockUi,
          project: stubProject,
          config: {
            redis: config
          },
          commandOptions: { },
          revisionData: {
            revisionKey: 'something-else'
          }
        };

        plugin.beforeHook(context);
        plugin.configure(context);
        assert.typeOf(config.revisionKey, 'function');
        assert.equal(config.revisionKey(context), 'something-else');
      });
    it('uses the default appId if one is not supplied', function() {
      delete adapterOptions.appId;

      var subject = new Adapter(adapterOptions);

      assert.equal(subject.appId, 'default');
    });
      it('uses the commandOptions value if it exists', function() {
        var plugin = subject.createDeployPlugin({
          name: 'tag-git'
        });

        var config = {
        };
        var context = {
          ui: mockUi,
          project: stubProject,
          config: {
            "tag-git": config
          },
          commandOptions: {
            revision: 'abcd'
          },
          revisionData: {
            revisionKey: 'something-else'
          }
        };

        plugin.beforeHook(context);
        plugin.configure(context);
        assert.typeOf(config.revisionKey, 'function');
        assert.equal(config.revisionKey(context), 'abcd');
      });
  it('has a name', function() {
    var result = subject.createDeployPlugin({
      name: 'test-plugin'
    });

    assert.equal(result.name, 'test-plugin');
  });
      it('passes config for specified alias to redis', function () {
        var plugin = subject.createDeployPlugin({
          name: 'foobar'
        });

        var redisLib = new FakeRedis();

        var config = {
          database: 7
        };
        var context = {
          ui: mockUi,
          project: stubProject,
          config: {
            foobar: config
          },
          _redisLib: redisLib
        };

        plugin.beforeHook(context);
        plugin.configure(context);
        plugin.readConfig('redisDeployClient');

        assert.equal(redisLib.createdClient.options.database, 7);
      });
    it('uses the default maximum version count if not supplied', function() {
      delete adapterOptions.versionCount;

      var subject = new Adapter(adapterOptions);

      assert.equal(subject.versionCount, 15);
    });
  it('has a name', function() {
    var plugin = subject.createDeployPlugin({
      name: 's3'
    });

    assert.equal(plugin.name, 's3');
  });
      it('uses the config data if it already exists', function() {
        var plugin = subject.createDeployPlugin({
          name: 'redis'
        });

        var config = {
          host: 'somehost',
          port: 1234,
          revisionKey: '12345'
        };
        var context = {
          ui: mockUi,
          project: stubProject,
          config: {
            redis: config
          },
          revisionData: {
            revisionKey: 'something-else'
          }
        };

        plugin.beforeHook(context);
        plugin.configure(context);
        assert.equal(plugin.readConfig('revisionKey'), '12345');
      });
 it('does not add default config to the config object', function() {
   plugin.configure(context);
   assert.isDefined(config.redis.host);
   assert.isDefined(config.redis.port);
   assert.isDefined(config.redis.filePattern);
   assert.isDefined(config.redis.didDeployMessage);
   assert.equal(config.redis.keyPrefix, 'proj:home');
 });
 .then(function() {
   assert.equal(mockUi.messages.length, 4);
   assert.match(mockUi.messages[0], /- Downloading manifest for differential deploy.../);
   assert.match(mockUi.messages[1], /- Manifest found. Differential deploy will be applied\./);
   assert.match(mockUi.messages[2], /- ✔  js-app\/app\.css/);
   assert.match(mockUi.messages[3], /- ✔  js-app\/manifest\.txt/);
   done();
 }).catch(function(reason){
 putObject: function(params, cb) {
   if (params.Key === 'app.css') {
     assert.equal(params.ContentEncoding, 'gzip');
     assertionCount++;
   } else {
     assert.isUndefined(params.ContentEncoding);
     assertionCount++;
   }
   cb();
 },
    it('throws an error if environment is not set', function() {
      try {
        new Config();
      } catch(e) {
        assert.equal(e.message, 'Configuration must define an `environment` property\n');
        return;
      }

      assert.ok(false, 'Should have thrown an exception due to no environment property');
    });
    it('throws error if initiated without config', function() {
      try {
        new Adapter();
      } catch(e) {
        assert.equal(e.message, 'Adapter must define a `connection` property\n');

        return;
      }

      assert.ok(false, 'Should have thrown an exception');
    });
      it('warns about missing optional filePattern, distDir, activationSuffix, revisionKey, didDeployMessage, maxNumberOfRecentUploads, and connection info', function() {
        plugin.configure(context);
        var messages = mockUi.messages.reduce(function(previous, current) {
          if (/- Missing config:\s.*, using default:\s/.test(current)) {
            previous.push(current);
          }

          return previous;
        }, []);
        assert.equal(messages.length, 11);
      });
      it('warns about missing optional filePattern, distDir, keyPrefix, revisionKey and didDeployMessage only', function() {
        plugin.configure(context);
        var messages = mockUi.messages.reduce(function(previous, current) {
          if (/- Missing config:\s.*, using default:\s/.test(current)) {
            previous.push(current);
          }

          return previous;
        }, []);
        assert.equal(messages.length, 6);
      });
      it('warns about missing optional config', function() {
        plugin.configure(context);
        var messages = mockUi.messages.reduce(function(previous, current) {
          if (/- Missing config:\s.*, using default:\s/.test(current)) {
            previous.push(current);
          }

          return previous;
        }, []);
        assert.equal(messages.length, 8);
      });
    it('throws an error if the config file does not exist', function() {
      try {
        new Config({
          project: { root: process.cwd() },
          environment: 'development'
        });
      } catch(e) {
        assert.equal(e.message, '`development` config does not exist\n');
        return;
      }

      assert.ok(false, 'Should have thrown an exception due to no config found');
    });
            .then(function() {
              assert.equal(serviceCalls.length, 2);

              var didActivateMessage = serviceCalls[0];
              var didDeployMessage   = serviceCalls[1];

              assert.deepEqual(didActivateMessage.body, { text: 'didActivate' });
              assert.deepEqual(didActivateMessage.url, webhookURL);
              assert.deepEqual(didActivateMessage.method, 'POST');

              assert.deepEqual(didDeployMessage.body, { text: 'didDeploy' });
              assert.deepEqual(didDeployMessage.url, webhookURL);
              assert.deepEqual(didDeployMessage.method, 'POST');
            });
 .then(function() {
   assert.equal(s3Params.Bucket, 'some-bucket');
   assert.equal(s3Params.ACL, 'public-read');
   assert.equal(s3Params.Body.toString(), 'some content here\n');
   assert.equal(s3Params.ContentType, 'application/javascript');
   assert.equal(s3Params.Key, 'js-app/app.js');
   assert.equal(s3Params.CacheControl, 'max-age=63072000, public');
   assert.deepEqual(s3Params.Expires, new Date('2030'));
 });
      it('warns about missing required config', function() {
        context.config = { deploySentry: {} };

        plugin.beforeHook(context);
        assert.throws(function(error){
          plugin.configure(context);
        });
        var messages = mockUi.messages.reduce(function(previous, current) {
          if (/- Missing required config:\s.*/.test(current)) {
            previous.push(current);
          }

          return previous;
        }, []);

        assert.equal(messages.length, 1); // doesn't log all failures, just first one
      });
    it("warns of services that are configured but have not hook turned on", function() {
      services.bugsnag = {
        apiKey: '1234'
      };

      services.slack = {
        webhookURL: '<your-webhook-url>'
      };

      plugin.beforeHook(context);
      plugin.configure(context);

      var promise = plugin.setup(context);
      var messages = mockUi.messages;

      assert.isAbove(messages.length, 0);
      assert.equal(messages[0], '- Warning! bugsnag - Service configuration found but no hook specified in deploy configuration. Service will not be notified.')
    });
    it('warns about missing optional config', function() {
      delete context.config.s3.region;
      delete context.config.s3.filePattern;
      delete context.config.s3.prefix;

      var plugin = subject.createDeployPlugin({
        name: 's3'
      });
      plugin.beforeHook(context);
      plugin.configure(context);
      var messages = mockUi.messages.reduce(function(previous, current) {
        if (/- Missing config:\s.*, using default:\s/.test(current)) {
          previous.push(current);
        }

        return previous;
      }, []);

      assert.equal(messages.length, 3);
    });