You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by at...@apache.org on 2016/08/10 10:36:52 UTC

[2/3] ambari git commit: AMBARI-18097 Cover configurations mixins with unit tests. (atkach)

http://git-wip-us.apache.org/repos/asf/ambari/blob/32ef4d4d/ambari-web/test/mixins/common/configs/configs_loader_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/configs/configs_loader_test.js b/ambari-web/test/mixins/common/configs/configs_loader_test.js
new file mode 100644
index 0000000..4154e8c
--- /dev/null
+++ b/ambari-web/test/mixins/common/configs/configs_loader_test.js
@@ -0,0 +1,497 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+var testHelpers = require('test/helpers');
+
+describe('App.ConfigsLoader', function() {
+	var mixin;
+
+	beforeEach(function() {
+    mixin = Em.Object.create(App.ConfigsLoader, {
+      content: Em.Object.create(),
+      trackRequest: Em.K,
+      parseConfigData: Em.K,
+      host: Em.Object.create(),
+      clearRecommendationsInfo: Em.K,
+      isVersionDefault: Em.K
+    });
+	});
+
+  describe("#clearLoadInfo()", function () {
+
+    it("allVersionsLoaded should be false", function() {
+      mixin.clearLoadInfo();
+      expect(mixin.get('allVersionsLoaded')).to.be.false;
+    });
+  });
+
+  describe("#loadServiceConfigVersions()", function () {
+
+    it("App.ajax.send should be called", function() {
+      mixin.set('content.serviceName', 'S1');
+      mixin.loadServiceConfigVersions();
+      expect(testHelpers.findAjaxRequest('name', 'service.serviceConfigVersions.get')[0]).to.eql({
+        name: 'service.serviceConfigVersions.get',
+        data: {
+          serviceName: 'S1'
+        },
+        sender: mixin,
+        success: 'loadServiceConfigVersionsSuccess'
+      });
+    });
+
+    it("allVersionsLoaded should be false", function() {
+      mixin.loadServiceConfigVersions();
+      expect(mixin.get('allVersionsLoaded')).to.be.false;
+    });
+  });
+
+  describe("#loadServiceConfigVersionsSuccess()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.serviceConfigVersionsMapper, 'map');
+      sinon.stub(mixin, 'loadPreSelectedConfigVersion');
+    });
+
+    afterEach(function() {
+      App.serviceConfigVersionsMapper.map.restore();
+      mixin.loadPreSelectedConfigVersion.restore();
+    });
+
+    it("data exist", function() {
+      mixin.loadServiceConfigVersionsSuccess({items: [{}]});
+      expect(App.serviceConfigVersionsMapper.map.calledWith({items: [{}]})).to.be.true;
+      expect(mixin.get('currentDefaultVersion')).to.be.null;
+    });
+
+    it("data contains current default version", function() {
+      var data = {items: [{
+        group_name: 'Default',
+        is_current: true,
+        service_config_version: 'v1'
+      }]};
+      mixin.loadServiceConfigVersionsSuccess(data);
+      expect(App.serviceConfigVersionsMapper.map.calledWith(data)).to.be.true;
+      expect(mixin.get('currentDefaultVersion')).to.be.equal('v1');
+    });
+
+    it("allVersionsLoaded should be true", function() {
+      mixin.loadServiceConfigVersionsSuccess({items: []});
+      expect(mixin.get('allVersionsLoaded')).to.be.true;
+    });
+
+    it("loadPreSelectedConfigVersion should be called", function() {
+      mixin.set('preSelectedConfigVersion', {});
+      mixin.loadServiceConfigVersionsSuccess({items: []});
+      expect(mixin.loadPreSelectedConfigVersion.calledOnce).to.be.true;
+    });
+
+    it("selectedVersion should be set", function() {
+      mixin.set('preSelectedConfigVersion', null);
+      mixin.set('currentDefaultVersion', 'v1');
+      mixin.loadServiceConfigVersionsSuccess({items: []});
+      expect(mixin.get('selectedVersion')).to.be.equal('v1');
+    });
+  });
+
+  describe("#loadPreSelectedConfigVersion()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.serviceConfigVersionsMapper, 'makeId').returns('v1');
+      this.mock = sinon.stub(App.ServiceConfigVersion, 'find');
+      sinon.stub(mixin, 'loadConfigGroups', function() {
+        return {done: function(callback) {
+          callback();
+        }}
+      });
+      sinon.stub(mixin, 'loadSelectedVersion');
+      sinon.stub(App.ServiceConfigGroup, 'find').returns([
+        Em.Object.create({
+          serviceName: 'S1',
+          name: 'G1',
+          isDefault: true
+        })
+      ]);
+      this.mock.returns([]);
+      this.mock.withArgs('v1').returns(Em.Object.create({
+        serviceName: 'S1',
+        groupName: 'G1',
+        version: 'v1'
+      }));
+    });
+
+    afterEach(function() {
+      App.serviceConfigVersionsMapper.makeId.restore();
+      this.mock.restore();
+      mixin.loadConfigGroups.restore();
+      mixin.loadSelectedVersion.restore();
+      App.ServiceConfigGroup.find.restore();
+    });
+
+    it("loadConfigGroups should be called", function() {
+      mixin.set('servicesToLoad', ['S1']);
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.loadConfigGroups.calledWith(['S1'])).to.be.true;
+    });
+
+    it("selectedVersion should be set", function() {
+      mixin.set('preSelectedConfigVersion', Em.Object.create({version: 'v1'}));
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('selectedVersion')).to.be.equal('v1');
+    });
+
+    it("selectedConfigGroup should be undefined", function() {
+      this.mock.withArgs('v1').returns(Em.Object.create({
+        serviceName: 'S1',
+        groupName: 'G2',
+        version: 'v1'
+      }));
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('selectedConfigGroup')).to.be.undefined;
+    });
+
+    it("selectedConfigGroup should be set", function() {
+      this.mock.withArgs('v1').returns(Em.Object.create({
+        serviceName: 'S1',
+        groupName: 'G1',
+        version: 'v1'
+      }));
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(Em.Object.create({
+        serviceName: 'S1',
+        name: 'G1',
+        isDefault: true
+      }));
+    });
+
+    it("default selectedConfigGroup should be set", function() {
+      this.mock.withArgs('v1').returns(Em.Object.create({
+        serviceName: 'S1',
+        groupName: 'Default',
+        version: 'v1'
+      }));
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(Em.Object.create({
+        serviceName: 'S1',
+        name: 'G1',
+        isDefault: true
+      }));
+    });
+
+    it("preselected selectedConfigGroup should be set", function() {
+      this.mock.returns([Em.Object.create({
+        id: 'v1',
+        serviceName: 'S1',
+        groupName: 'G1',
+        version: 'v1'
+      })]);
+      mixin.set('preSelectedConfigVersion', Em.Object.create({
+        serviceName: 'S1',
+        groupName: 'G1',
+        version: 'v1'
+      }));
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(Em.Object.create({
+        serviceName: 'S1',
+        name: 'G1',
+        isDefault: true
+      }));
+    });
+
+    it("loadSelectedVersion should be called", function() {
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.loadSelectedVersion.calledOnce).to.be.true;
+    });
+
+    it("preSelectedConfigVersion should be null", function() {
+      mixin.loadPreSelectedConfigVersion();
+      expect(mixin.get('preSelectedConfigVersion')).to.be.null;
+    });
+  });
+
+  describe("#loadCurrentVersions()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'trackRequest');
+      mixin.set('currentDefaultVersion', {});
+      mixin.set('servicesToLoad', ['S1', 'S2']);
+      mixin.loadCurrentVersions();
+    });
+
+    afterEach(function() {
+      mixin.trackRequest.restore();
+    });
+
+    it("isCompareMode should be false", function() {
+      expect(mixin.get('isCompareMode')).to.be.false;
+    });
+
+    it("versionLoaded should be false", function() {
+      expect(mixin.get('versionLoaded')).to.be.false;
+    });
+
+    it("selectedVersion should be set", function() {
+      expect(mixin.get('versionLoaded')).to.be.an.object;
+    });
+
+    it("preSelectedConfigVersion should be null", function() {
+      expect(mixin.get('preSelectedConfigVersion')).to.be.null;
+    });
+
+    it("trackRequest should be called", function() {
+      expect(mixin.trackRequest.calledOnce).to.be.true;
+    });
+
+    it("App.ajax.send should be called", function() {
+      expect(testHelpers.findAjaxRequest('name', 'service.serviceConfigVersions.get.current')[0]).to.eql({
+        name: 'service.serviceConfigVersions.get.current',
+        sender: mixin,
+        data: {
+          serviceNames: 'S1,S2'
+        },
+        success: 'loadCurrentVersionsSuccess'
+      });
+    });
+  });
+
+  describe("#loadCurrentVersionsSuccess()", function () {
+    var group = Em.Object.create({
+      isDefault: false,
+      hosts: ['host1']
+    });
+    var defaultGroup = Em.Object.create({
+      isDefault: true
+    });
+
+    beforeEach(function() {
+      sinon.stub(App.configGroupsMapper, 'map');
+      sinon.stub(mixin, 'loadConfigGroups').returns({
+        done: function(callback) {callback();}
+      });
+      sinon.stub(App.ServiceConfigGroup, 'find').returns([group, defaultGroup]);
+      sinon.stub(mixin, 'parseConfigData');
+    });
+
+    afterEach(function() {
+      App.configGroupsMapper.map.restore();
+      mixin.loadConfigGroups.restore();
+      App.ServiceConfigGroup.find.restore();
+      mixin.parseConfigData.restore();
+    });
+
+    it("App.configGroupsMapper.map should be called", function() {
+      mixin.loadCurrentVersionsSuccess({}, {}, {serviceNames: 'S1,S2'});
+      expect(App.configGroupsMapper.map.calledWith({}, true, ['S1', 'S2'])).to.be.true;
+    });
+
+    it("parseConfigData should be called", function() {
+      mixin.loadCurrentVersionsSuccess({}, {}, {serviceNames: 'S1,S2'});
+      expect(mixin.parseConfigData.calledWith({})).to.be.true;
+    });
+
+    it("isHostsConfigsPage=true, non-default group", function() {
+      mixin.set('isHostsConfigsPage', true);
+      mixin.set('host.hostName', 'host1');
+      mixin.loadCurrentVersionsSuccess({}, {}, {serviceNames: 'S1,S2'});
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(group);
+    });
+
+    it("isHostsConfigsPage=true, default group", function() {
+      mixin.set('isHostsConfigsPage', true);
+      mixin.loadCurrentVersionsSuccess({}, {}, {serviceNames: 'S1,S2'});
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(defaultGroup);
+    });
+
+    it("isHostsConfigsPage=false", function() {
+      mixin.set('isHostsConfigsPage', false);
+      mixin.loadCurrentVersionsSuccess({}, {}, {serviceNames: 'S1,S2'});
+      expect(mixin.get('selectedConfigGroup')).to.be.eql(defaultGroup);
+    });
+  });
+
+  describe("#loadSelectedVersion()", function () {
+
+    beforeEach(function() {
+      mixin.set('currentDefaultVersion', 'v1');
+      sinon.stub(mixin, 'clearRecommendationsInfo');
+      sinon.stub(mixin, 'loadCurrentVersions');
+      sinon.stub(mixin, 'loadDefaultGroupVersion');
+    });
+
+    afterEach(function() {
+      mixin.clearRecommendationsInfo.restore();
+      mixin.loadCurrentVersions.restore();
+      mixin.loadDefaultGroupVersion.restore();
+    });
+
+    it("isCompareMode should be false", function() {
+      mixin.loadSelectedVersion();
+      expect(mixin.get('isCompareMode')).to.be.false;
+    });
+
+    it("versionLoaded should be false", function() {
+      mixin.loadSelectedVersion();
+      expect(mixin.get('versionLoaded')).to.be.false;
+    });
+
+    it("clearRecommendationsInfo should be called", function() {
+      mixin.loadSelectedVersion();
+      expect(mixin.clearRecommendationsInfo.calledOnce).to.be.true;
+    });
+
+    it("loadDefaultGroupVersion should be called", function() {
+      mixin.loadSelectedVersion('v2', {});
+      expect(mixin.loadDefaultGroupVersion.calledWith('v2', {})).to.be.true;
+    });
+
+    it("loadCurrentVersions should be called", function() {
+      mixin.loadSelectedVersion('v1', null);
+      expect(mixin.loadCurrentVersions.calledOnce).to.be.true;
+      expect(mixin.get('selectedVersion', 'v1'));
+    });
+
+    it("loadCurrentVersions should be called, default", function() {
+      mixin.loadSelectedVersion('v1', Em.Object.create({isDefault: true}));
+      expect(mixin.loadCurrentVersions.calledOnce).to.be.true;
+      expect(mixin.get('selectedVersion', 'v1'));
+    });
+  });
+
+  describe("#loadDefaultGroupVersion()", function () {
+
+    beforeEach(function() {
+      mixin.set('content.serviceName', 'S1');
+      this.mock = sinon.stub(mixin, 'isVersionDefault');
+      sinon.stub(mixin, 'setSelectedConfigGroup');
+    });
+
+    afterEach(function() {
+      this.mock.restore();
+      mixin.setSelectedConfigGroup.restore();
+    });
+
+    it("setSelectedConfigGroup should be called", function() {
+      mixin.loadDefaultGroupVersion('v1', {});
+      expect(mixin.setSelectedConfigGroup.calledWith('v1', {})).to.be.true;
+    });
+
+    it("default version", function() {
+      mixin.set('dependentServiceNames', 'S2');
+      this.mock.returns(true);
+      mixin.loadDefaultGroupVersion('v1', {});
+      expect(mixin.get('selectedVersion')).to.be.equal('v1');
+      expect(testHelpers.findAjaxRequest('name', 'service.serviceConfigVersions.get.multiple')[0]).to.eql({
+        name: 'service.serviceConfigVersions.get.multiple',
+        sender: mixin,
+        data: {
+          serviceName: 'S1',
+          serviceConfigVersions: ['v1'],
+          additionalParams: '|service_name.in(S2)&is_current=true'
+        },
+        success: 'loadSelectedVersionsSuccess'
+      });
+    });
+
+    it("non-default version", function() {
+      mixin.set('dependentServiceNames', '');
+      mixin.set('currentDefaultVersion', 'v1');
+      this.mock.returns(false);
+      mixin.loadDefaultGroupVersion('v2', {});
+      expect(mixin.get('selectedVersion')).to.be.equal('v2');
+      expect(testHelpers.findAjaxRequest('name', 'service.serviceConfigVersions.get.multiple')[0]).to.eql({
+        name: 'service.serviceConfigVersions.get.multiple',
+        sender: mixin,
+        data: {
+          serviceName: 'S1',
+          serviceConfigVersions: ['v1', 'v2'],
+          additionalParams: ''
+        },
+        success: 'loadSelectedVersionsSuccess'
+      });
+    });
+  });
+
+  describe("#setSelectedConfigGroup()", function () {
+
+    beforeEach(function() {
+      mixin.set('configGroups', [{isDefault: true}]);
+      mixin.set('selectedConfigGroup', null);
+      this.mock = sinon.stub(mixin, 'isVersionDefault');
+    });
+
+    afterEach(function() {
+      mixin.isVersionDefault.restore();
+    });
+
+    var testCases = [
+      {
+        dataIsLoaded: true,
+        isVersionDefault: true,
+        switchToGroup: null,
+        expected: {isDefault: true}
+      },
+      {
+        dataIsLoaded: true,
+        isVersionDefault: false,
+        switchToGroup: {},
+        expected: {}
+      },
+      {
+        dataIsLoaded: true,
+        isVersionDefault: true,
+        switchToGroup: null,
+        expected: null
+      },
+      {
+        dataIsLoaded: false,
+        isVersionDefault: false,
+        switchToGroup: {},
+        expected: null
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("dataIsLoaded = " +test.dataIsLoaded +
+         "isVersionDefault = " + test.isVersionDefault +
+         "switchToGroup = "+ test.switchToGroup , function() {
+        this.mock.returns(test.isVersionDefault);
+        mixin.setSelectedConfigGroup('v1', test.switchToGroup);
+      });
+    });
+  });
+
+  describe("#loadSelectedVersionsSuccess()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'parseConfigData');
+    });
+
+    afterEach(function() {
+      mixin.parseConfigData.restore();
+    });
+
+    it("parseConfigData should be called", function() {
+      mixin.loadSelectedVersionsSuccess({});
+      expect(mixin.parseConfigData.calledWith({})).to.be.true;
+    });
+  });
+
+});
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/32ef4d4d/ambari-web/test/mixins/common/configs/configs_saver_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/configs/configs_saver_test.js b/ambari-web/test/mixins/common/configs/configs_saver_test.js
index 74e630b..6e65cf9 100644
--- a/ambari-web/test/mixins/common/configs/configs_saver_test.js
+++ b/ambari-web/test/mixins/common/configs/configs_saver_test.js
@@ -17,10 +17,37 @@
  */
 
 var App = require('app');
+var testHelpers = require('test/helpers');
 
 describe('App.ConfigsSaverMixin', function() {
-  var mixinObject = Em.Controller.extend(App.ConfigsSaverMixin, {});
-  var instanceObject = mixinObject.create({});
+  var mixin;
+
+  beforeEach(function() {
+    mixin = Em.Object.create(App.ConfigsSaverMixin, {
+      content: Em.Object.create(),
+      showChangedDependentConfigs: Em.K,
+      serverSideValidation: Em.K,
+      getHash: Em.K,
+      clearAllRecommendations: Em.K
+    });
+  });
+
+  describe("#currentServices", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.StackService, 'find').returns({});
+    });
+
+    afterEach(function() {
+      App.StackService.find.restore();
+    });
+
+    it("should return currentServices", function() {
+      mixin.set('content.serviceName', 'S1');
+      expect(mixin.get('currentServices')).to.be.eql([{}]);
+      expect(App.StackService.find.calledWith('S1')).to.be.true;
+    });
+  });
 
   describe('#allowSaveCoreSite()', function () {
     var allowedType = 'CORETYPE';
@@ -40,7 +67,7 @@ describe('App.ConfigsSaverMixin', function() {
       })
     ];
     beforeEach(function () {
-      instanceObject.setProperties({
+      mixin.setProperties({
         'content': {},
         'coreSiteServiceType': allowedType,
         'coreSiteServiceNames': allowedService
@@ -62,11 +89,11 @@ describe('App.ConfigsSaverMixin', function() {
     }].forEach(function (c, index) {
         describe(c.m, function () {
           beforeEach(function () {
-            instanceObject.reopen({
+            mixin.reopen({
               currentServices: c.currentServices
             });
             it('test #' + index, function () {
-              expect(instanceObject.allowSaveCoreSite()).to.be.equal(c.res);
+              expect(mixin.allowSaveCoreSite()).to.be.equal(c.res);
             });
           });
         });
@@ -82,13 +109,13 @@ describe('App.ConfigsSaverMixin', function() {
     ].forEach(function (c, index) {
         describe(c.m, function () {
           beforeEach(function() {
-            sinon.stub(instanceObject, 'allowSaveCoreSite').returns(c.allowSaveCoreSite);
+            sinon.stub(mixin, 'allowSaveCoreSite').returns(c.allowSaveCoreSite);
           });
           afterEach(function() {
-            instanceObject.allowSaveCoreSite.restore();
+            mixin.allowSaveCoreSite.restore();
           });
           it('test #' + index, function () {
-            expect(instanceObject.allowSaveSite(c.fName)).to.equal(c.res);
+            expect(mixin.allowSaveSite(c.fName)).to.equal(c.res);
           });
         });
       });
@@ -96,20 +123,20 @@ describe('App.ConfigsSaverMixin', function() {
 
   describe('#createDesiredConfig()', function() {
     beforeEach(function() {
-      sinon.stub(instanceObject, 'formatValueBeforeSave', function(property) {
+      sinon.stub(mixin, 'formatValueBeforeSave', function(property) {
         return property.get('value');
       })
     });
     afterEach(function() {
-      instanceObject.formatValueBeforeSave.restore();
+      mixin.formatValueBeforeSave.restore();
     });
 
     it('generates config wil throw error', function() {
-      expect(instanceObject.createDesiredConfig.bind(instanceObject)).to.throw(Error, 'assertion failed');
+      expect(mixin.createDesiredConfig.bind(mixin)).to.throw(Error, 'assertion failed');
     });
 
     it('generates config without properties', function() {
-      expect(instanceObject.createDesiredConfig('type1', 'version1')).to.eql({
+      expect(mixin.createDesiredConfig('type1', 'version1')).to.eql({
         "type": 'type1',
         "tag": 'version1',
         "properties": {},
@@ -118,7 +145,7 @@ describe('App.ConfigsSaverMixin', function() {
     });
 
     it('generates config with properties', function() {
-      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: true})], "note")).to.eql({
+      expect(mixin.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: true})], "note")).to.eql({
         "type": 'type1',
         "tag": 'version1',
         "properties": {
@@ -130,7 +157,7 @@ describe('App.ConfigsSaverMixin', function() {
     });
 
     it('generates config with properties and skip isRequiredByAgent', function() {
-      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: false})], "note")).to.eql({
+      expect(mixin.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: false})], "note")).to.eql({
         "type": 'type1',
         "tag": 'version1',
         "properties": {
@@ -141,7 +168,7 @@ describe('App.ConfigsSaverMixin', function() {
     });
 
     it('generates config with properties and skip service_config_version_note', function() {
-      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true})], "note", true)).to.eql({
+      expect(mixin.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true})], "note", true)).to.eql({
         "type": 'type1',
         "tag": 'version1',
         "properties": {
@@ -151,7 +178,7 @@ describe('App.ConfigsSaverMixin', function() {
     });
 
     it('generates config with final, password, user, group, text, additional_user_property, not_managed_hdfs_path, value_from_property_file', function() {
-      expect(instanceObject.createDesiredConfig('type1', 'version1', [
+      expect(mixin.createDesiredConfig('type1', 'version1', [
           Em.Object.create({name: 'p1', value: 'v1', isFinal: true, isRequiredByAgent: true}),
           Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: true}),
           Em.Object.create({name: 'p3', value: 'v3', isRequiredByAgent: true, propertyType: ["PASSWORD", "USER", "GROUP"]}),
@@ -207,28 +234,28 @@ describe('App.ConfigsSaverMixin', function() {
 
   describe('#generateDesiredConfigsJSON()', function() {
     beforeEach(function() {
-      sinon.stub(instanceObject, 'createDesiredConfig', function(type) {
+      sinon.stub(mixin, 'createDesiredConfig', function(type) {
         return 'desiredConfig_' + type;
       });
-      sinon.stub(instanceObject, 'allowSaveSite', function() {
+      sinon.stub(mixin, 'allowSaveSite', function() {
         return true;
       });
 
     });
     afterEach(function() {
-      instanceObject.createDesiredConfig.restore();
-      instanceObject.allowSaveSite.restore();
+      mixin.createDesiredConfig.restore();
+      mixin.allowSaveSite.restore();
     });
 
     it('generates empty array as data is missing', function() {
-      expect(instanceObject.generateDesiredConfigsJSON()).to.eql([]);
-      expect(instanceObject.generateDesiredConfigsJSON(1,1)).to.eql([]);
-      expect(instanceObject.generateDesiredConfigsJSON([],[])).to.eql([]);
+      expect(mixin.generateDesiredConfigsJSON()).to.eql([]);
+      expect(mixin.generateDesiredConfigsJSON(1,1)).to.eql([]);
+      expect(mixin.generateDesiredConfigsJSON([],[])).to.eql([]);
     });
 
     it('generates array with desired configs', function() {
-      expect(instanceObject.generateDesiredConfigsJSON([Em.Object.create({'name': 'p1', 'fileName': 'f1.xml'})], ['f1'])).to.eql(['desiredConfig_f1']);
-      expect(instanceObject.createDesiredConfig).to.be.calledOnce
+      expect(mixin.generateDesiredConfigsJSON([Em.Object.create({'name': 'p1', 'fileName': 'f1.xml'})], ['f1'])).to.eql(['desiredConfig_f1']);
+      expect(mixin.createDesiredConfig).to.be.calledOnce
     })
   });
 
@@ -261,15 +288,894 @@ describe('App.ConfigsSaverMixin', function() {
       })
     ];
     it('filter out changed configs', function () {
-      expect(instanceObject.getModifiedConfigs(configs).mapProperty('name')).to.eql(['p1','p2','p3']);
-      expect(instanceObject.getModifiedConfigs(configs).mapProperty('filename').uniq()).to.eql(['f1','f2']);
+      expect(mixin.getModifiedConfigs(configs).mapProperty('name')).to.eql(['p1','p2','p3']);
+      expect(mixin.getModifiedConfigs(configs).mapProperty('filename').uniq()).to.eql(['f1','f2']);
     });
 
     it('filter out changed configs and modifiedFileNames', function () {
-      instanceObject.set('modifiedFileNames', ['f3']);
-      expect(instanceObject.getModifiedConfigs(configs).mapProperty('name')).to.eql(['p1','p2','p3','p4']);
-      expect(instanceObject.getModifiedConfigs(configs).mapProperty('filename').uniq()).to.eql(['f1','f2','f3']);
+      mixin.set('modifiedFileNames', ['f3']);
+      expect(mixin.getModifiedConfigs(configs).mapProperty('name')).to.eql(['p1','p2','p3','p4']);
+      expect(mixin.getModifiedConfigs(configs).mapProperty('filename').uniq()).to.eql(['f1','f2','f3']);
+    });
+  });
+
+  describe("#getGroupFromModel()", function () {
+
+    beforeEach(function() {
+      this.mock = sinon.stub(App.ServiceConfigGroup, 'find');
+    });
+
+    afterEach(function() {
+      App.ServiceConfigGroup.find.restore();
+    });
+
+    it("should return selectedConfigGroup", function() {
+      mixin.set('selectedService', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      mixin.set('selectedConfigGroup', Em.Object.create({name: 'g1'}));
+      expect(mixin.getGroupFromModel('S1')).to.be.eql(Em.Object.create({name: 'g1'}));
+    });
+
+    it("selectedConfigGroup.isDefault=true, service has no groups", function() {
+      mixin.set('selectedService', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      this.mock.returns([]);
+      mixin.set('selectedConfigGroup', Em.Object.create({name: 'g1', isDefault: true}));
+      expect(mixin.getGroupFromModel('S2')).to.be.null;
+    });
+
+    it("selectedConfigGroup.isDefault=true, service has groups", function() {
+      mixin.set('selectedService', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      this.mock.returns([Em.Object.create({serviceName: 'S2', isDefault: true})]);
+      mixin.set('selectedConfigGroup', Em.Object.create({name: 'g1', isDefault: true}));
+      expect(mixin.getGroupFromModel('S2')).to.be.eql(Em.Object.create({serviceName: 'S2', isDefault: true}));
+    });
+
+    it("selectedConfigGroup.isDefault=false, service has no groups", function() {
+      mixin.set('selectedService', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      this.mock.returns([]);
+      mixin.set('selectedConfigGroup', Em.Object.create({name: 'g1', isDefault: false}));
+      expect(mixin.getGroupFromModel('S2')).to.be.null;
+    });
+
+    it("selectedConfigGroup.isDefault=false, service has groups", function() {
+      mixin.set('selectedService', Em.Object.create({
+        serviceName: 'S1'
+      }));
+      this.mock.returns([Em.Object.create({serviceName: 'S2', isDefault: true, name: 'g2'})]);
+      mixin.set('selectedConfigGroup', Em.Object.create({
+        name: 'g1',
+        isDefault: false,
+        dependentConfigGroups: {
+          'S2': 'g2'
+        }
+      }));
+      expect(mixin.getGroupFromModel('S2')).to.be.eql(Em.Object.create({
+        serviceName: 'S2',
+        isDefault: true,
+        name: 'g2'
+      }));
+    });
+  });
+
+  describe("#saveConfigs()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'saveConfigsForDefaultGroup');
+      sinon.stub(mixin, 'saveConfigsForNonDefaultGroup');
+    });
+
+    afterEach(function() {
+      mixin.saveConfigsForDefaultGroup.restore();
+      mixin.saveConfigsForNonDefaultGroup.restore();
+    });
+
+    it("saveConfigsForDefaultGroup should be called", function() {
+      mixin.set('selectedConfigGroup', Em.Object.create({isDefault: true}));
+      mixin.saveConfigs();
+      expect(mixin.saveConfigsForDefaultGroup.calledOnce).to.be.true;
+    });
+
+    it("saveConfigsForNonDefaultGroup should be called", function() {
+      mixin.set('selectedConfigGroup', Em.Object.create({isDefault: false}));
+      mixin.saveConfigs();
+      expect(mixin.saveConfigsForNonDefaultGroup.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#saveConfigsForNonDefaultGroup()", function () {
+
+    beforeEach(function() {
+      this.mockGroup = sinon.stub(mixin, 'getGroupFromModel');
+      this.mockConfigs = sinon.stub(mixin, 'getConfigsForGroup');
+      sinon.stub(mixin, 'saveGroup');
+    });
+
+    afterEach(function() {
+      this.mockGroup.restore();
+      this.mockConfigs.restore();
+      mixin.saveGroup.restore();
+    });
+
+    it("configGroup is null", function() {
+      mixin.set('stepConfigs', [Em.Object.create()]);
+      this.mockGroup.returns(null);
+      mixin.saveConfigsForNonDefaultGroup();
+      expect(mixin.saveGroup.called).to.be.false;
+    });
+
+    it("configGroup is default", function() {
+      mixin.set('stepConfigs', [Em.Object.create()]);
+      this.mockGroup.returns(Em.Object.create({isDefault: true}));
+      mixin.saveConfigsForNonDefaultGroup();
+      expect(mixin.saveGroup.called).to.be.false;
+    });
+
+    it("overriddenConfigs is null", function() {
+      mixin.set('stepConfigs', [Em.Object.create()]);
+      this.mockGroup.returns(Em.Object.create({isDefault: false}));
+      this.mockConfigs.returns(null);
+      mixin.saveConfigsForNonDefaultGroup();
+      expect(mixin.saveGroup.called).to.be.false;
+    });
+
+    it("saveGroup should be called, current service", function() {
+      mixin.set('stepConfigs', [Em.Object.create({serviceName: 'S1'})]);
+      mixin.set('serviceConfigVersionNote', 'note');
+      mixin.set('content.serviceName', 'S1');
+      this.mockGroup.returns(Em.Object.create({isDefault: false}));
+      this.mockConfigs.returns([{}]);
+      mixin.saveConfigsForNonDefaultGroup();
+      expect(mixin.saveGroup.calledWith(
+        [{}],
+        Em.Object.create({isDefault: false}),
+        'note',
+        'putConfigGroupChangesSuccess'
+      )).to.be.true;
+    });
+
+    it("saveGroup should be called, another service", function() {
+      mixin.set('stepConfigs', [Em.Object.create({serviceName: 'S1'})]);
+      mixin.set('serviceConfigVersionNote', 'note');
+      mixin.set('content.serviceName', 'S2');
+      this.mockGroup.returns(Em.Object.create({isDefault: false}));
+      this.mockConfigs.returns([{}]);
+      mixin.saveConfigsForNonDefaultGroup();
+      expect(mixin.saveGroup.calledWith(
+        [{}],
+        Em.Object.create({isDefault: false}),
+        'note',
+        null
+      )).to.be.true;
+    });
+  });
+
+  describe("#saveConfigsForDefaultGroup()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'getServiceConfigToSave').returns({});
+      sinon.stub(mixin, 'putChangedConfigurations');
+      sinon.stub(mixin, 'onDoPUTClusterConfigurations');
+    });
+
+    afterEach(function() {
+      mixin.getServiceConfigToSave.restore();
+      mixin.putChangedConfigurations.restore();
+      mixin.onDoPUTClusterConfigurations.restore();
+    });
+
+    it("putChangedConfigurations should be called", function() {
+      mixin.set('stepConfigs', [Em.Object.create()]);
+      mixin.saveConfigsForDefaultGroup();
+      expect(mixin.putChangedConfigurations.calledWith([{}], 'doPUTClusterConfigurationSiteSuccessCallback')).to.be.true;
+    });
+
+    it("onDoPUTClusterConfigurations should be called", function() {
+      mixin.set('stepConfigs', []);
+      mixin.saveConfigsForDefaultGroup();
+      expect(mixin.onDoPUTClusterConfigurations.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#completeSave()", function () {
+
+    it("saveInProgress should be false", function() {
+      mixin.completeSave();
+      expect(mixin.get('saveInProgress')).to.be.false;
+    });
+  });
+
+  describe("#showWarningPopupsBeforeSave()", function () {
+
+    beforeEach(function() {
+      this.mock = sinon.stub(mixin, 'isDirChanged');
+      sinon.stub(App, 'showConfirmationPopup', Em.clb);
+      sinon.stub(mixin, 'showChangedDependentConfigs', function(arg1, callback) {
+        callback();
+      });
+      sinon.stub(mixin, 'restartServicePopup');
+    });
+
+    afterEach(function() {
+      this.mock.restore();
+      App.showConfirmationPopup.restore();
+      mixin.showChangedDependentConfigs.restore();
+      mixin.restartServicePopup.restore();
+    });
+
+    it("directory changed", function() {
+      this.mock.returns(true);
+      mixin.showWarningPopupsBeforeSave();
+      expect(App.showConfirmationPopup.calledOnce).to.be.true;
+      expect(mixin.showChangedDependentConfigs.calledOnce).to.be.true;
+      expect(mixin.restartServicePopup.calledOnce).to.be.true;
+    });
+
+    it("directory didn't change", function() {
+      this.mock.returns(false);
+      mixin.showWarningPopupsBeforeSave();
+      expect(mixin.showChangedDependentConfigs.calledOnce).to.be.true;
+      expect(mixin.restartServicePopup.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#restartServicePopup()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'serverSideValidation').returns({
+        done: Em.K,
+        fail: Em.K
+      });
+    });
+
+    afterEach(function() {
+      mixin.serverSideValidation.restore();
+    });
+
+    it("serverSideValidation should be called", function() {
+      mixin.restartServicePopup();
+      expect(mixin.serverSideValidation.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#getConfigsForGroup()", function () {
+
+    it("empty stepConfigs", function() {
+      var stepConfigs = [];
+      expect(mixin.getConfigsForGroup(stepConfigs, 'g1')).to.be.empty;
+    });
+
+    it("stepConfigs has overrides", function() {
+      var stepConfigs = [Em.Object.create({
+        overrides: [
+          Em.Object.create({
+            group: {name: 'g1'}
+          })
+        ]
+      })];
+      expect(mixin.getConfigsForGroup(stepConfigs, 'g1')).to.be.eql([
+        Em.Object.create({
+          group: {name: 'g1'}
+        })
+      ]);
+    });
+
+    it("stepConfigs has group", function() {
+      var stepConfigs = [
+        Em.Object.create({
+          group: {name: 'g1'}
+        }),
+        Em.Object.create({
+          group: {name: 'g2'}
+        })
+      ];
+      expect(mixin.getConfigsForGroup(stepConfigs, 'g1')).to.be.eql([
+        Em.Object.create({
+          group: {name: 'g1'}
+        })
+      ]);
+    });
+  });
+
+  describe("#saveSiteConfigs()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'formatConfigValues');
+    });
+
+    afterEach(function() {
+      mixin.formatConfigValues.restore();
+    });
+
+    it("formatConfigValues should be called", function() {
+      expect(mixin.saveSiteConfigs([{}])).to.be.eql([{}]);
+      expect(mixin.formatConfigValues.calledWith([{}])).to.be.true;
+    });
+  });
+
+  describe("#allowSaveCoreSite()", function () {
+
+    it("empty currentServices", function() {
+      mixin.set('currentServices', []);
+      expect(mixin.allowSaveCoreSite()).to.be.false;
+    });
+
+    it("coreSiteServiceNames has current service", function() {
+      mixin.set('coreSiteServiceNames', ['S1']);
+      mixin.reopen({
+        currentServices: [Em.Object.create({serviceName: 'S1'})]
+      });
+      expect(mixin.allowSaveCoreSite()).to.be.true;
+    });
+
+    it("coreSiteServiceType equal currentService", function() {
+      mixin.set('coreSiteServiceType', 't1');
+      mixin.reopen({
+        currentServices: [Em.Object.create({serviceName: 'S1', serviceType: 't1'})]
+      });
+      expect(mixin.allowSaveCoreSite()).to.be.true;
+    });
+  });
+
+  describe("#clearSaveInfo()", function () {
+
+    it("modifiedFileNames should be empty", function() {
+      mixin.clearSaveInfo();
+      expect(mixin.get('modifiedFileNames')).to.be.empty;
+    });
+  });
+
+  describe("#saveStepConfigs()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'startSave');
+      sinon.stub(mixin, 'showWarningPopupsBeforeSave');
+    });
+
+    afterEach(function() {
+      mixin.startSave.restore();
+      mixin.showWarningPopupsBeforeSave.restore();
+    });
+
+    it("isSubmitDisabled = false", function() {
+      mixin.set('isSubmitDisabled', false);
+      mixin.saveStepConfigs();
+      expect(mixin.startSave.calledOnce).to.be.true;
+      expect(mixin.showWarningPopupsBeforeSave.calledOnce).to.be.true;
+    });
+
+    it("isSubmitDisabled = true", function() {
+      mixin.set('isSubmitDisabled', true);
+      mixin.saveStepConfigs();
+      expect(mixin.startSave.called).to.be.false;
+      expect(mixin.showWarningPopupsBeforeSave.called).to.be.false;
+    });
+  });
+
+  describe("#startSave()", function () {
+
+    it("saveInProgress should be true", function() {
+      mixin.startSave();
+      expect(mixin.get('saveInProgress')).to.be.true;
+    });
+  });
+
+  describe("#hasUnsavedChanges()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'getHash').returns('hash2');
+    });
+
+    afterEach(function() {
+      mixin.getHash.restore();
+    });
+
+    it("hash is null", function() {
+      mixin.set('hash', null);
+      expect(mixin.hasUnsavedChanges()).to.be.false;
+    });
+
+    it("hash the same", function() {
+      mixin.set('hash', 'hash2');
+      expect(mixin.hasUnsavedChanges()).to.be.false;
+    });
+
+    it("hash different", function() {
+      mixin.set('hash', 'hash1');
+      expect(mixin.hasUnsavedChanges()).to.be.true;
     });
   });
+
+  describe("#isDirChanged()", function () {
+
+    var testCases = [
+      {
+        configs: [],
+        serviceName: 'S1',
+        expected: false
+      },
+      {
+        configs: [],
+        serviceName: 'HDFS',
+        expected: false
+      },
+      {
+        configs: [
+          Em.Object.create({
+            name: 'dfs.namenode.name.dir',
+            isNotDefaultValue: false
+          })
+        ],
+        serviceName: 'HDFS',
+        expected: false
+      },
+      {
+        configs: [
+          Em.Object.create({
+            name: 'dfs.namenode.name.dir',
+            isNotDefaultValue: true
+          })
+        ],
+        serviceName: 'HDFS',
+        expected: true
+      },
+      {
+        configs: [
+          Em.Object.create({
+            name: 'dfs.namenode.checkpoint.dir',
+            isNotDefaultValue: true
+          })
+        ],
+        serviceName: 'HDFS',
+        expected: true
+      },
+      {
+        configs: [
+          Em.Object.create({
+            name: 'dfs.datanode.data.dir',
+            isNotDefaultValue: true
+          })
+        ],
+        serviceName: 'HDFS',
+        expected: true
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("serviceName = " + test.serviceName +
+         "configs = " + test.configs, function() {
+        mixin.set('stepConfigs', [Em.Object.create({
+          serviceName: test.serviceName,
+          configs: test.configs
+        })]);
+        mixin.set('content.serviceName', test.serviceName);
+        expect(mixin.isDirChanged()).to.be.equal(test.expected);
+      });
+    });
+  });
+
+  describe("#formatConfigValues()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.config, 'trimProperty', function(c) {
+        return c.get('value');
+      });
+    });
+
+    afterEach(function() {
+      App.config.trimProperty.restore();
+    });
+
+    it("boolean value", function() {
+      var config = Em.Object.create({value: true});
+      mixin.formatConfigValues([config]);
+      expect(config.get('value')).to.be.equal('true');
+    });
+
+    it("string value", function() {
+      var config = Em.Object.create({value: 'val1'});
+      mixin.formatConfigValues([config]);
+      expect(config.get('value')).to.be.equal('val1');
+    });
+  });
+
+  describe("#formatValueBeforeSave()", function () {
+
+    beforeEach(function() {
+      this.mock = sinon.stub(mixin, 'addM').returns(false);
+      sinon.stub(App.router, 'get').returns({
+        't1': 'val1'
+      });
+      sinon.stub(App.config, 'trimProperty').returns('trimmed');
+    });
+
+    afterEach(function() {
+      this.mock.restore();
+      App.router.get.restore();
+      App.config.trimProperty.restore();
+    });
+
+    it("should add 'm'", function() {
+      this.mock.returns(true);
+      var property = Em.Object.create({name: 'p1', value: 'val1'});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal('val1m');
+    });
+
+    it("boolean value", function() {
+      var property = Em.Object.create({name: 'p1', value: true});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal('true');
+    });
+
+    it("kdc_type config", function() {
+      var property = Em.Object.create({name: 'kdc_type', value: 'val1'});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal('t1');
+    });
+
+    it("storm.zookeeper.servers config", function() {
+      var property = Em.Object.create({name: 'storm.zookeeper.servers', value: 'val1'});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal('val1');
+    });
+
+    it("nimbus.seeds config", function() {
+      var property = Em.Object.create({name: 'storm.zookeeper.servers', value: ['val1']});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal("['val1']");
+    });
+
+    it("other config", function() {
+      var property = Em.Object.create({name: 'p1', value: 'val1'});
+      expect(mixin.formatValueBeforeSave(property)).to.be.equal("trimmed");
+    });
+  });
+
+  describe("#addM()", function () {
+
+    var testCases = [
+      {
+        name: 'aa_heapsize',
+        value: 'val1',
+        expected: true
+      },
+      {
+        name: 'aa_heapsiz',
+        value: 'val1',
+        expected: false
+      },
+      {
+        name: 'hadoop_heapsize',
+        value: 'val1',
+        expected: false
+      },
+      {
+        name: 'aa_heapsize',
+        value: 'val1m',
+        expected: false
+      }
+    ];
+
+    testCases.forEach(function(test) {
+      it("name = " + test.name +
+         " value = " + test.value, function() {
+        expect(mixin.addM(test.name, test.value)).to.be.equal(test.expected);
+      });
+    });
+  });
+
+  describe("#saveGroup()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'generateDesiredConfigsJSON').returns("{}");
+      sinon.stub(mixin, 'createConfigGroup');
+      sinon.stub(mixin, 'updateConfigGroup');
+    });
+
+    afterEach(function() {
+      mixin.generateDesiredConfigsJSON.restore();
+      mixin.createConfigGroup.restore();
+      mixin.updateConfigGroup.restore();
+    });
+
+    it("createConfigGroup should be called", function() {
+      var group = {
+        name: 'g1',
+        is_temporary: true,
+        service_id: 'S1',
+        description: 'desc',
+        hosts: ['host1']
+      };
+      mixin.saveGroup([], group, 'note', Em.K);
+      expect(mixin.createConfigGroup.getCall(0).args[0]).to.be.eql({
+        ConfigGroup: {
+          "cluster_name": 'mycluster',
+          "group_name": 'g1',
+          "tag": 'S1',
+          "description": 'desc',
+          "hosts": [{
+            host_name: 'host1'
+          }],
+          "service_config_version_note": "note",
+          "desired_configs": "{}"
+        }
+      });
+    });
+
+    it("updateConfigGroup should be called", function() {
+      var group = {
+        id: 'g1',
+        name: 'g1',
+        is_temporary: false,
+        service_id: 'S1',
+        description: 'desc',
+        hosts: ['host1']
+      };
+      mixin.saveGroup([], group, null, Em.K);
+      expect(mixin.updateConfigGroup.getCall(0).args[0]).to.be.eql({
+        ConfigGroup: {
+          "cluster_name": 'mycluster',
+          "group_name": 'g1',
+          "tag": 'S1',
+          "description": 'desc',
+          "hosts": [{
+            host_name: 'host1'
+          }],
+          "service_config_version_note": "",
+          "desired_configs": "{}",
+          id: 'g1'
+        }
+      });
+    });
+  });
+
+  describe("#createConfigGroup()", function () {
+
+    it("App.ajax.send should be called", function() {
+      mixin.createConfigGroup({}, null);
+      var args = testHelpers.findAjaxRequest('name', 'wizard.step8.apply_configuration_groups');
+      expect(args[0]).to.be.eql({
+        name: 'wizard.step8.apply_configuration_groups',
+        sender: mixin,
+        data: {
+          data: '{}'
+        }
+      });
+    });
+  });
+
+  describe("#updateConfigGroup()", function () {
+
+    it("App.ajax.send should be called", function() {
+      mixin.updateConfigGroup({ConfigGroup: {id: 'g1'}}, null);
+      var args = testHelpers.findAjaxRequest('name', 'config_groups.update_config_group');
+      expect(args[0]).to.be.eql({
+        name: 'config_groups.update_config_group',
+        sender: mixin,
+        data: {
+          id: 'g1',
+          configGroup: {ConfigGroup: {id: 'g1'}}
+        }
+      });
+    });
+  });
+
+  describe("#putChangedConfigurations()", function () {
+
+    it("App.ajax.send should be called", function() {
+      mixin.putChangedConfigurations(['S1'], null, null);
+      var args = testHelpers.findAjaxRequest('name', 'common.across.services.configurations');
+      expect(args[0]).to.be.eql({
+        name: 'common.across.services.configurations',
+        sender: mixin,
+        data: {
+          data: '[S1]'
+        },
+        error: 'doPUTClusterConfigurationSiteErrorCallback'
+      });
+    });
+  });
+
+  describe("#putConfigGroupChangesSuccess()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'onDoPUTClusterConfigurations');
+    });
+
+    afterEach(function() {
+      mixin.onDoPUTClusterConfigurations.restore();
+    });
+
+    it("onDoPUTClusterConfigurations should be called", function() {
+      mixin.putConfigGroupChangesSuccess();
+      expect(mixin.get('saveConfigsFlag')).to.be.true;
+      expect(mixin.onDoPUTClusterConfigurations.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#doPUTClusterConfigurationSiteSuccessCallback()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'onDoPUTClusterConfigurations');
+    });
+
+    afterEach(function() {
+      mixin.onDoPUTClusterConfigurations.restore();
+    });
+
+    it("onDoPUTClusterConfigurations should be called", function() {
+      mixin.doPUTClusterConfigurationSiteSuccessCallback();
+      expect(mixin.onDoPUTClusterConfigurations.calledWith(true)).to.be.true;
+    });
+  });
+
+  describe("#doPUTClusterConfigurationSiteErrorCallback()", function () {
+
+    beforeEach(function() {
+      sinon.stub(mixin, 'onDoPUTClusterConfigurations');
+    });
+
+    afterEach(function() {
+      mixin.onDoPUTClusterConfigurations.restore();
+    });
+
+    it("onDoPUTClusterConfigurations should be called", function() {
+      mixin.doPUTClusterConfigurationSiteErrorCallback();
+      expect(mixin.get('saveConfigsFlag')).to.be.false;
+      expect(mixin.onDoPUTClusterConfigurations.calledOnce).to.be.true;
+    });
+  });
+
+  describe("#onDoPUTClusterConfigurations", function () {
+    var mockController = {
+      updateClusterData: Em.K,
+      updateComponentConfig: Em.K,
+      triggerQuickLinksUpdate: Em.K,
+      loadConfigs: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(mockController);
+      sinon.stub(App.Service, 'find').returns({});
+      sinon.stub(mockController, 'updateClusterData');
+      sinon.stub(mockController, 'updateComponentConfig');
+      sinon.stub(mockController, 'triggerQuickLinksUpdate');
+      sinon.stub(mockController, 'loadConfigs');
+      sinon.stub(mixin, 'showSaveConfigsPopup');
+      sinon.stub(mixin, 'getSaveConfigsPopupOptions').returns({
+        header: 'h1',
+        message: 'm1',
+        messageClass: 'mc1',
+        value: 'val1',
+        urlParams: ''
+      });
+      sinon.stub(mixin, 'clearAllRecommendations');
+      mixin.set('saveConfigsFlag', false);
+      mixin.onDoPUTClusterConfigurations(true);
+    });
+
+    afterEach(function() {
+      App.Service.find.restore();
+      App.router.get.restore();
+      mockController.updateClusterData.restore();
+      mockController.updateComponentConfig.restore();
+      mockController.triggerQuickLinksUpdate.restore();
+      mockController.loadConfigs.restore();
+      mixin.showSaveConfigsPopup.restore();
+      mixin.getSaveConfigsPopupOptions.restore();
+      mixin.clearAllRecommendations.restore();
+    });
+
+    it("updateClusterData should be called", function() {
+      expect(mockController.updateClusterData.calledOnce).to.be.true;
+    });
+
+    it("updateComponentConfig should be called", function() {
+      expect(mockController.updateComponentConfig.calledOnce).to.be.true;
+    });
+
+    it("triggerQuickLinksUpdate should be called", function() {
+      expect(mockController.triggerQuickLinksUpdate.calledOnce).to.be.true;
+    });
+
+    it("loadConfigs should be called", function() {
+      expect(mockController.loadConfigs.calledOnce).to.be.true;
+    });
+
+    it("clearAllRecommendations should be called", function() {
+      expect(mixin.clearAllRecommendations.calledOnce).to.be.true;
+    });
+
+    it("showSaveConfigsPopup should be called", function() {
+      expect(mixin.showSaveConfigsPopup.getCall(0).args).to.be.eql([
+        'h1', false, 'm1', 'mc1', 'val1', 'unknown', '', true
+      ]);
+    });
+  });
+
+  describe("#getSaveConfigsPopupOptions()", function () {
+
+    it("flag is true", function() {
+      mixin.set('content.serviceName', 'HDFS');
+      expect(mixin.getSaveConfigsPopupOptions({flag: true})).to.be.eql({
+        header: Em.I18n.t('services.service.config.saved'),
+        message: Em.I18n.t('services.service.config.saved.message'),
+        messageClass: 'alert alert-success',
+        urlParams: ',ServiceComponentInfo/installed_count,ServiceComponentInfo/total_count&ServiceComponentInfo/service_name.in(HDFS)'
+      });
+    });
+
+    it("flag is false", function() {
+      expect(mixin.getSaveConfigsPopupOptions({flag: false, message: 'm1', value: 'val1'})).to.be.eql({
+        urlParams: '',
+        header: Em.I18n.t('common.failure'),
+        message: 'm1',
+        messageClass: 'alert alert-error',
+        value: 'val1'
+      });
+    });
+  });
+
+  describe("#getServiceConfigToSave()", function () {
+
+    beforeEach(function() {
+      sinon.stub(App.config, 'textareaIntoFileConfigs');
+      sinon.stub(App.StackService, 'find').returns(Em.Object.create({
+        configTypes: {'k1': 't1'}
+      }));
+      sinon.stub(App.config, 'getOriginalFileName').returns('file1');
+      this.mockSave = sinon.stub(mixin, 'saveSiteConfigs');
+      this.mockJSON = sinon.stub(mixin, 'generateDesiredConfigsJSON');
+      sinon.stub(mixin, 'getModifiedConfigs').returns([]);
+    });
+
+    afterEach(function() {
+      App.config.textareaIntoFileConfigs.restore();
+      App.StackService.find.restore();
+      App.config.getOriginalFileName.restore();
+      this.mockSave.restore();
+      this.mockJSON.restore();
+      mixin.getModifiedConfigs.restore();
+    });
+
+    it("App.config.textareaIntoFileConfigs should be called", function() {
+      mixin.getServiceConfigToSave('YARN', [{}]);
+      expect(App.config.textareaIntoFileConfigs.calledWith([{}], 'capacity-scheduler.xml')).to.be.true;
+    });
+
+    it("getModifiedConfigs should be called", function() {
+      mixin.getServiceConfigToSave('S1', [{}]);
+      expect(mixin.getModifiedConfigs.calledWith([{}])).to.be.true;
+    });
+
+    it("modifiedConfigs is null", function() {
+      this.mockSave.returns(null);
+      expect(mixin.getServiceConfigToSave('S1', [{}])).to.be.null;
+    });
+
+    it("modifiedConfigs is empty", function() {
+      this.mockSave.returns([]);
+      expect(mixin.getServiceConfigToSave('S1', [{}])).to.be.null;
+    });
+
+    it("configsToSave is empty", function() {
+      this.mockSave.returns([{filename: 'file1'}]);
+      this.mockJSON.returns([]);
+      expect(mixin.getServiceConfigToSave('S1', [{}])).to.be.null;
+    });
+
+    it("configsToSave has configs", function() {
+      this.mockSave.returns([{filename: 'file1'}]);
+      this.mockJSON.returns([{}]);
+      expect(mixin.getServiceConfigToSave('S1', [{}])).to.be.equal(JSON.stringify({
+        Clusters: {
+          desired_config: [{}]
+        }
+      }));
+    });
+  });
+
 });