/*
 *    (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
 *
 * Licensed 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.
 */
(function() {
  'use strict';

  describe('Launch Instance Model', function() {

    describe('launchInstanceModel Factory', function() {
      var model, scope, settings, $q, glance, IMAGE, VOLUME, VOLUME_SNAPSHOT, INSTANCE_SNAPSHOT;
      var cinderEnabled = false;
      var neutronEnabled = false;
      var ifAllowedResolve = true;

      var novaApi = {
        createServer: function(finalSpec) {
          return {
            then: function () {
              return finalSpec;
            }
          };
        },
        getAvailabilityZones: function() {
          var zones = [
            { zoneName: 'zone-1', zoneState: { available: true } },
            { zoneName: 'zone-2', zoneState: { available: true } },
            { zoneName: 'invalid-zone-1' },
            { zoneName: 'invalid-zone-2' }
          ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: zones } });

          return deferred.promise;
        },
        getFlavors: function() {
          var flavors = [ 'flavor-1', 'flavor-2' ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: flavors } });

          return deferred.promise;
        },
        getKeypairs: function() {
          var keypairs = [ { keypair: { name: 'key-1' } },
                           { keypair: { name: 'key-2' } } ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: keypairs } });

          return deferred.promise;
        },
        getLimits: function() {
          var limits = { maxTotalInstances: 10, totalInstancesUsed: 0 };

          var deferred = $q.defer();
          deferred.resolve({ data: limits });

          return deferred.promise;
        },
        getServerGroups: function() {
          var serverGroups = [ {'id': 'group-1'}, {'id': 'group-2'} ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: serverGroups } });

          return deferred.promise;
        }
      };

      var securityGroupApi = {
        query: function() {
          var secGroups = [
            { name: 'security-group-1' },
            { name: 'security-group-2' }
          ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: secGroups } });
          return deferred.promise;
        }
      };

      var neutronApi = {
        getNetworks: function() {
          var networks = [
            { id: 'net-1', subnets: [ { id: 'subnet1' } ] },
            { id: 'net-2', subnets: [ { id: 'subnet2' } ] },
            { id: 'net-3', subnets: []}
          ];

          var deferred = $q.defer();
          deferred.resolve({ data: { items: networks } });

          return deferred.promise;
        },
        getPorts: function(network) {
          var ports = {
            'net-1': [
              { name: 'port-1', device_owner: '', fixed_ips: [], admin_state: 'UP' },
              { name: 'port-2', device_owner: '', fixed_ips: [], admin_state: 'DOWN' }
            ],
            'net-2': [
              { name: 'port-3', device_owner: 'owner', fixed_ips: [], admin_state: 'DOWN' },
              { name: 'port-4', device_owner: '', fixed_ips: [], admin_state: 'DOWN' }
            ],
            'net-3': []
          };

          var deferred = $q.defer();
          deferred.resolve({ data: { items: ports[network.network_id] } });

          return deferred.promise;
        }
      };

      beforeEach(module('horizon.dashboard.project.workflow.launch-instance'));

      beforeEach(module(function($provide) {
        $provide.value('horizon.app.core.openstack-service-api.glance', {
          getImages: function (params) {
            var images;
            if (params.visibility === 'community') {
              images = [
                {id: '10', container_format: 'raw', properties: {image_type: 'image'},
                 visibility: 'community'},
                // ID 6 is already returned by the first call (below), so this should be ignored.
                // To clarify the difference, the content here is different intentionally.
                {id: '6', container_format: 'raw', properties: {image_type: 'image'},
                 visibility: 'community'},
              ];
            } else {
              images = [
                // container_format aki and ari are not considered as bootable
                {id: '1', container_format: 'aki', properties: {}},
                {id: '2', container_format: 'ari', properties: {}},
                // The following images are considered as "image" sources.
                {id: '3', container_format: 'ami', properties: {}, name: 'ami_image'},
                {id: '4', container_format: 'raw', properties: {}, name: 'raw_image'},
                {id: '5', container_format: 'ami', properties: {image_type: 'image'}},
                {id: '6', container_format: 'raw', properties: {image_type: 'image'}},
                {id: '11', container_format: 'raw', properties: {image_type: ''}},
                {id: '12', container_format: 'raw', properties: {block_device_mapping: '[]'}},
                // The following images are considered as "snapshot" sources.
                {id: '7', container_format: 'ami',
                 properties: {block_device_mapping: '[{"source_type": "snapshot"}]'}},
                {id: '8', container_format: 'raw',
                 properties: {block_device_mapping: '[{"source_type": "snapshot"}]'}},
                {id: '9', container_format: 'raw', properties: {image_type: 'snapshot'}},
              ];
            }

            var deferred = $q.defer();
            deferred.resolve({data: {items: images}});

            return deferred.promise;
          },
          getNamespaces: function () {
            var namespaces = ['ns-1', 'ns-2'];

            var deferred = $q.defer();
            deferred.resolve({data: {items: namespaces}});

            return deferred.promise;
          }
        });

        settings = {
          LAUNCH_INSTANCE_DEFAULTS: {
            create_volume: true,
            hide_create_volume: false,
            config_drive: false,
            disable_image: false,
            disable_instance_snapshot: false,
            disable_volume: false,
            disable_volume_snapshot: false,
            default_availability_zone: 'Any'
          },
          DEFAULT_BOOT_SOURCE: 'image'
        };
        IMAGE = {type: 'image', label: 'Image', selected: true};
        VOLUME = {type: 'volume', label: 'Volume', selected: false};
        VOLUME_SNAPSHOT = {type: 'volume_snapshot', label: 'Volume Snapshot', selected: false};
        INSTANCE_SNAPSHOT = {type: 'snapshot', label: 'Instance Snapshot', selected: false};

        $provide.value('horizon.app.core.openstack-service-api.nova', novaApi);

        $provide.value('horizon.app.core.openstack-service-api.security-group', securityGroupApi);

        $provide.value('horizon.app.core.openstack-service-api.neutron', neutronApi);

        $provide.value('horizon.app.core.openstack-service-api.cinder', {
          getVolumes: function() {
            var volumes = [ { id: 'vol-1' }, { id: 'vol-2' } ];

            var deferred = $q.defer();
            deferred.resolve({ data: { items: volumes } });

            return deferred.promise;
          },
          getVolumeSnapshots: function() {
            var snapshots = [ { id: 'snap-1', volume_id: 'vol-1' },
                              { id: 'snap-2', volume_id: 'vol-2' },
                              { id: 'snap-3', volume_id: 'vol-3' } ];

            var deferred = $q.defer();
            deferred.resolve({ data: { items: snapshots } });

            return deferred.promise;
          },
          getAbsoluteLimits: function() {
            var limits = { maxTotalVolumes: 100,
                           totalVolumesUsed: 2,
                           maxTotalVolumeGigabytes: 1000,
                           totalGigabytesUsed: 10 };
            var deferred = $q.defer();
            deferred.resolve({ data: limits });
            return deferred.promise;
          }
        });

        $provide.value('horizon.app.core.openstack-service-api.serviceCatalog', {
          ifTypeEnabled: function(theType) {
            var deferred = $q.defer();

            if (theType === 'network' && neutronEnabled) {
              deferred.resolve();
            } else if (theType === 'volumev3' && cinderEnabled) {
              deferred.resolve();
            } else {
              deferred.reject();
            }

            return deferred.promise;
          }
        });

        $provide.value('horizon.app.core.openstack-service-api.policy', {
          ifAllowed: function() {
            var deferred = $q.defer();

            if (ifAllowedResolve) {
              deferred.resolve();
            } else {
              deferred.reject();
            }
            return deferred.promise;
          },
          check: function() {
            var deferred = $q.defer();

            deferred.resolve();

            return deferred.promise;
          }
        });

        $provide.value('horizon.app.core.openstack-service-api.settings', {
          getSetting: function(setting) {
            var deferred = $q.defer();

            deferred.resolve(settings[setting]);

            return deferred.promise;
          },
          ifEnabled: function(setting) {
            var deferred = $q.defer();

            var keys = setting.split('.');
            var index = 0;
            var value = settings;
            while (angular.isObject(value) && index < keys.length) {
              value = value[keys[index]];
              index++;
            }

            // NOTE: This does not work for the general case of ifEnabled, only for what
            // we need it for at the moment (only explicit false rejects the promise).
            if (value === false) {
              deferred.reject();
            } else {
              deferred.resolve();
            }

            return deferred.promise;
          }
        });

        $provide.value('horizon.framework.widgets.toast.service', {
          add: function() {}
        });
      }));

      beforeEach(inject(function($injector) {
        model = $injector.get('launchInstanceModel');
        $q = $injector.get('$q');
        scope = $injector.get('$rootScope').$new();
        glance = $injector.get('horizon.app.core.openstack-service-api.glance');
        spyOn(glance, 'getNamespaces').and.callThrough();
        spyOn(novaApi, 'getServerGroups').and.callThrough();
      }));

      describe('Initial object (pre-initialize)', function() {

        it('is defined', function() {
          expect(model).toBeDefined();
        });

        it('has initialization status parameters', function() {
          expect(model.initializing).toBeDefined();
          expect(model.initialized).toBeDefined();
        });

        it('has an empty newInstanceSpec', function() {
          expect(model.newInstanceSpec).toEqual({});
        });

        it('has empty arrays for all data', function() {
          var datasets = ['availabilityZones', 'flavors', 'allowedBootSources',
            'images', 'imageSnapshots', 'keypairs', 'networks',
            'securityGroups', 'serverGroups', 'volumes',
            'volumeSnapshots'];

          datasets.forEach(function(name) {
            expect(model[name]).toEqual([]);
          });
        });

        it('initialized metadatadefs to null values', function() {
          expect(model.metadataDefs.flavor).toBeNull();
          expect(model.metadataDefs.image).toBeNull();
          expect(model.metadataDefs.volume).toBeNull();
          expect(model.metadataDefs.instance).toBeNull();
          expect(model.metadataDefs.hints).toBeNull();
          expect(Object.keys(model.metadataDefs).length).toBe(5);
        });

        it('defaults "allow create volume from image" to false', function() {
          expect(model.allowCreateVolumeFromImage).toBe(false);
        });

        it('defaults "volume bootable" to false', function() {
          expect(model.volumeBootable).toBe(false);
        });

        it('defaults "metadataTree" to null', function() {
          expect(model.metadataTree).toBe(null);
        });

        it('defaults "hintsTree" to null', function() {
          expect(model.hintsTree).toBe(null);
        });

        it('initializes "nova limits" to empty object', function() {
          expect(model.novaLimits).toEqual({});
        });

        it('has an "initialize" function', function() {
          expect(model.initialize).toBeDefined();
        });

        it('has a "createInstance" function', function() {
          expect(model.createInstance).toBeDefined();
        });

      });

      describe('Post Initialize Model', function() {

        it('should init model with no networks/volumes if neutron & cinder disabled', function() {
          model.initialize(true);
          scope.$apply();

          expect(model.initializing).toBe(false);
          expect(model.initialized).toBe(true);
          expect(model.newInstanceSpec).toBeDefined();

          var expectedImages = [
            {id: '3', container_format: 'ami', properties: {}, name: 'ami_image',
             name_or_id: 'ami_image'},
            {id: '4', container_format: 'raw', properties: {}, name: 'raw_image',
             name_or_id: 'raw_image'},
            {id: '5', container_format: 'ami', properties: {image_type: 'image'}, name_or_id: '5'},
            {id: '6', container_format: 'raw', properties: {image_type: 'image'}, name_or_id: '6'},
            {id: '11', container_format: 'raw', properties: {image_type: ''}, name_or_id: '11'},
            {id: '12', container_format: 'raw', properties: {block_device_mapping: '[]'},
             name_or_id: '12'},
            {id: '10', container_format: 'raw', properties: {image_type: 'image'}, name_or_id: '10',
             visibility: 'community'},
          ];
          expect(model.images).toEqual(expectedImages);

          var expectedSnapshots = [
            {id: '7', container_format: 'ami',
             properties: {block_device_mapping: '[{"source_type": "snapshot"}]'}},
            {id: '8', container_format: 'raw',
             properties: {block_device_mapping: '[{"source_type": "snapshot"}]'}},
            {id: '9', container_format: 'raw', properties: {image_type: 'snapshot'}},
          ];
          expect(model.imageSnapshots).toEqual(expectedSnapshots);

          var expectedZones = [
            {'label': 'Any Availability Zone', 'value': ''},
            {'label': 'zone-1', 'value': 'zone-1'},
            {'label': 'zone-2', 'value': 'zone-2'}
          ];
          expect(model.availabilityZones).toEqual(expectedZones);

          var expectedFlavors = ['flavor-1', 'flavor-2'];
          expect(model.flavors).toEqual(expectedFlavors);

          var expectedKeypairs = [
            {'name': 'key-1', id: 'li_keypair:key-1'},
            {'name': 'key-2', id: 'li_keypair:key-2'}
          ];
          expect(model.keypairs).toEqual(expectedKeypairs);

          var expectedSecurityGroups = [
            {name: 'security-group-1'},
            {name: 'security-group-2'}
          ];
          expect(model.securityGroups).toEqual(expectedSecurityGroups);

          var expectedLimits = {maxTotalInstances: 10, totalInstancesUsed: 0};
          expect(model.novaLimits).toEqual(expectedLimits);
        });

        it('should have networks & no volumes if neutron enabled & cinder disabled', function() {
          neutronEnabled = true;
          cinderEnabled = false;
          model.initialize(true);
          scope.$apply();

          expect(model.neutronEnabled).toBe(true);
          expect(model.networks.length).toBe(2);
          expect(model.volumes.length).toBe(0);
        });

        it('should have volumes & no networks if neutron disabled & cinder enabled', function() {
          neutronEnabled = false;
          cinderEnabled = true;
          model.initialize(true);
          scope.$apply();

          expect(model.networks.length).toBe(0);
          expect(model.volumes.length).toBe(2);
          expect(model.volumeSnapshots.length).toBe(2);
        });

        it('should have networks and volumes if neutron and cinder enabled', function() {
          neutronEnabled = true;
          cinderEnabled = true;
          model.initialize(true);
          scope.$apply();

          expect(model.networks.length).toBe(2);
          expect(model.volumes.length).toBe(2);
          expect(model.volumeSnapshots.length).toBe(2);
        });

        it('should default config_drive to false', function() {
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.config_drive).toBe(false);
        });

        it('should default config_drive to false if setting not provided', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.config_drive;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.config_drive).toBe(false);
        });

        it('should default config_drive to true based on setting', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.config_drive = true;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.config_drive).toBe(true);
        });

        it('should default create_volume to true if setting not provided', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.create_volume;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.create_volume_default).toBe(true);
        });

        it('should default create_volume to false based on setting', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.create_volume = false;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.create_volume_default).toBe(false);
        });

        it('should default availability_zone to empty based on default', function() {
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toBe('');
        });

        it('should default availability_zone to empty based on setting', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.default_availability_zone = 'Any';
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toBe('');
        });

        it('should default availability_zone based on setting', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.default_availability_zone = 'zone-1';
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toEqual('zone-1');
        });

        it('should default AZ to empty if AZ configured is not found', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.default_availability_zone = 'non-existing';
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toEqual('');
        });

        it('should default hide_create_volume to false if setting not provided', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.hide_create_volume;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.hide_create_volume).toBe(false);
        });

        it('should default hide_create_volume to true based on setting', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.hide_create_volume = true;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.hide_create_volume).toBe(true);
        });

        it('should default user_data based on setting', function() {
          settings.OPENSTACK_SERVER_DEFAULT_USER_DATA = 'default-data';
          model.initialize(true);
          scope.$apply();
          expect(model.newInstanceSpec.default_user_data).toBe('default-data');
        });

        it('should not set availability zone if the zone list is empty', function () {
          spyOn(novaApi, 'getAvailabilityZones').and.callFake(function () {
            var deferred = $q.defer();
            deferred.resolve({ data: { items: [] } });
            return deferred.promise;
          });
          model.initialize(true);
          scope.$apply();
          expect(model.availabilityZones.length).toBe(0);
          expect(model.newInstanceSpec.availability_zone).toBe(null);
        });

        it('sets the ports properly based on device_owner', function () {
          model.initialize(true);
          scope.$apply();
          expect(model.ports.length).toBe(1);
        });

        it('should make 5 requests for namespaces', function() {
          model.initialize(true);
          scope.$apply();
          expect(glance.getNamespaces.calls.count()).toBe(5);
        });

////        Rejections need to be tested differently with AngularJS 1.8.2.
//        it('should not request scheduler hints if scheduler hints disabled', function() {
//          settings.LAUNCH_INSTANCE_DEFAULTS.enable_scheduler_hints = false;
//          model.initialize(true);
//          scope.$apply();
//          expect(glance.getNamespaces.calls.count()).toBe(4);
//        });

        it('should set a keypair by default if only one keypair is available', function () {
          var keypair = { keypair: { name: 'key-1' } };
          spyOn(novaApi, 'getKeypairs').and.callFake(function () {
            var deferred = $q.defer();
            deferred.resolve({ data: { items: [ keypair ] } });
            return deferred.promise;
          });
          model.initialize(true);
          scope.$apply();
          expect(model.newInstanceSpec.key_pair.length).toBe(1);
          expect(model.newInstanceSpec.key_pair).toEqual([ keypair.keypair ]);
        });

        it('should set a security group by default if one named "default" is available',
          function () {
            var secGroups = [ { name: 'default' } ];
            spyOn(securityGroupApi, 'query').and.callFake(function () {
              var deferred = $q.defer();
              deferred.resolve({ data: { items: secGroups } });
              return deferred.promise;
            });
            model.initialize(true);
            scope.$apply();
            expect(model.newInstanceSpec.security_groups.length).toBe(1);
            expect(model.newInstanceSpec.security_groups).toEqual(secGroups);
          }
        );

        it('should set a network by default if only one network is available', function () {
          var networks = [ { id: 'net-1', subnets: [ { id: 'subnet1' } ] } ];
          spyOn(neutronApi, 'getNetworks').and.callFake(function () {
            var deferred = $q.defer();
            deferred.resolve({ data: { items: networks } });
            return deferred.promise;
          });
          model.initialize(true);
          scope.$apply();
          expect(model.newInstanceSpec.networks.length).toBe(1);
          expect(model.newInstanceSpec.networks).toEqual(networks);
        });

        it('getPorts at launch should not return child port', function () {
          var ports = [ { id: 'parent',
                          trunk_details:  { trunk_id: 'trunk1',
                                            sub_ports: [ { port_id: 'child' } ] } },
                        { id: 'child' },
                        { id : 'plain' } ];
          var networks = [ { id: 'net-1', subnets: [ { id: 'subnet1' } ] } ];
          spyOn(neutronApi, 'getNetworks').and.callFake(function () {
            var deferred = $q.defer();
            deferred.resolve({ data: { items: networks } });
            return deferred.promise;
          });
          spyOn(neutronApi, 'getPorts').and.callFake(function () {
            var deferred = $q.defer();
            deferred.resolve({ data: { items: ports } });
            return deferred.promise;
          });
          neutronEnabled = true;
          model.initialize(true);
          scope.$apply();
          expect(model.newInstanceSpec.ports.length).toBe(0);
        });

        it('should have the proper entries in allowedBootSources', function() {
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
        });

        it('should have proper availability_zone if specific setting is missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.default_availability_zone;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toBe('');
        });

        it('should have proper availability_zone if settings are missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS;
          model.initialize(true);
          scope.$apply();

          expect(model.newInstanceSpec.availability_zone).toBe('');
        });

        it('should have proper allowedBootSources if settings are missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have proper allowedBootSources if specific settings missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.create_volume;
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_image;
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_instance_snapshot;
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_volume;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
          expect(model.newInstanceSpec.create_volume_default).toBe(true);
        });

        it('should have no images if disable_image is set to true', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_image = true;
          model.initialize(true);
          scope.$apply();

          expect(model.images.length).toBe(0);
          expect(model.images).toEqual([]);
          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(3);
          expect(model.allowedBootSources).not.toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have images if disable_image is missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_image;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have no volumes if disable_volume is set to true', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_volume = true;
          model.initialize(true);
          scope.$apply();

          expect(model.volumes.length).toBe(0);
          expect(model.volumes).toEqual([]);
          expect(model.volumeSnapshots.length).toBe(2);
          expect(model.volumeSnapshots).toEqual([{ id: 'snap-1', volume_id: 'vol-1' },
                                                 { id: 'snap-2', volume_id: 'vol-2' }]);
          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(3);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).not.toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have volumes if disable_volume is missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_volume;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have volume snapshots if disable_volume_snapshot is missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_volume_snapshot;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should not have volume snapshots if disable_volume_snapshot is set to true',
        function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_volume_snapshot = true;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(3);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).not.toContain(VOLUME_SNAPSHOT);
        });

        it('should have no snapshot if disable_instance_snapshot is set to true', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_instance_snapshot = true;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(3);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).not.toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have snapshot if disable_instance_snapshot is missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_instance_snapshot;
          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have no snapshot and no image if both are disabled', function() {
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_image = true;
          settings.LAUNCH_INSTANCE_DEFAULTS.disable_instance_snapshot = true;

          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(2);
          expect(model.allowedBootSources).not.toContain(IMAGE);
          expect(model.allowedBootSources).not.toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have snapshot and image if both are missing', function() {
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_image;
          delete settings.LAUNCH_INSTANCE_DEFAULTS.disable_instance_snapshot;

          model.initialize(true);
          scope.$apply();

          expect(model.allowedBootSources).toBeDefined();
          expect(model.allowedBootSources.length).toBe(4);
          expect(model.allowedBootSources).toContain(IMAGE);
          expect(model.allowedBootSources).toContain(INSTANCE_SNAPSHOT);
          expect(model.allowedBootSources).toContain(VOLUME);
          expect(model.allowedBootSources).toContain(VOLUME_SNAPSHOT);
        });

        it('should have maxTotalVolumes and maxTotalVolumeGigabytes if cinder ' +
           'is enabled', function() {
          cinderEnabled = true;
          model.initialize(true);
          scope.$apply();

          expect(model.cinderLimits.maxTotalVolumes).toBe(100);
          expect(model.cinderLimits.maxTotalVolumeGigabytes).toBe(1000);
        });

////        Rejections need to be tested differently with AngularJS 1.8.2.
//        it('should not fetch server groups if the policy does not allow it', function () {
//          ifAllowedResolve = false;
//          model.initialize(true);
//          scope.$apply();
//          expect(novaApi.getServerGroups.calls.count()).toBe(0);
//        });

        it('should fetch server groups if the policy allows it', function () {
          ifAllowedResolve = true;
          model.initialize(true);
          scope.$apply();
          expect(novaApi.getServerGroups.calls.count()).toBe(1);
        });
      });

      describe('Post Initialization Model - Initializing', function() {

        beforeEach(function() {
          model.initializing = true;
          model.initialize(true);  // value and return don't matter
        });

        // This is here to ensure that as people add/change items, they
        // don't forget to implement tests for them.
        it('has the right number of properties', function() {
          expect(Object.keys(model.newInstanceSpec).length).toBe(23);
        });

        it('sets availability zone to null', function() {
          expect(model.newInstanceSpec.availability_zone).toBeNull();
        });

        it('sets admin pass to null', function() {
          expect(model.newInstanceSpec.admin_pass).toBeNull();
        });

        it('sets description to null', function() {
          expect(model.newInstanceSpec.description).toBeNull();
        });

        it('sets config drive to false', function() {
          expect(model.newInstanceSpec.config_drive).toBe(false);
        });

        it('sets create volume to true', function() {
          expect(model.newInstanceSpec.create_volume_default).toBe(true);
        });

        it('sets user data to an empty string', function() {
          expect(model.newInstanceSpec.user_data).toBe('');
        });

        it('sets default user data to an empty string', function() {
          expect(model.newInstanceSpec.default_user_data).toBe('');
        });

        it('sets disk config to AUTO', function() {
          expect(model.newInstanceSpec.disk_config).toBe('AUTO');
        });

        it('sets flavor to be null', function() {
          expect(model.newInstanceSpec.flavor).toBeNull();
        });

        it('sets instance count to 1', function() {
          expect(model.newInstanceSpec.instance_count).toBe(1);
        });

        it('sets key pair to an empty array', function() {
          expect(model.newInstanceSpec.key_pair).toEqual([]);
        });

        it('sets name to null', function() {
          expect(model.newInstanceSpec.name).toBeNull();
        });

        it('sets networks to an empty array', function() {
          expect(model.newInstanceSpec.networks).toEqual([]);
        });

        it('sets ports to an empty array', function() {
          expect(model.newInstanceSpec.ports).toEqual([]);
        });

        it('sets security groups to an empty array', function() {
          expect(model.newInstanceSpec.security_groups).toEqual([]);
        });

        it('sets scheduler hints to an empty object', function() {
          expect(model.newInstanceSpec.scheduler_hints).toEqual({});
        });

        it('sets source type to null', function() {
          expect(model.newInstanceSpec.source_type).toBeNull();
        });

        it('sets source to an empty array', function() {
          expect(model.newInstanceSpec.source).toEqual([]);
        });

        it('sets volume options appropriately', function() {
          expect(model.newInstanceSpec.vol_create).toBe(false);
          expect(model.newInstanceSpec.vol_delete_on_instance_delete).toBe(false);
          expect(model.newInstanceSpec.vol_size).toBe(1);
        });

      });

      describe('Create Instance', function() {
        var metadata, hints;

        beforeEach(function() {
          // initialize some data
          model.newInstanceSpec.source_type = { type: 'image' };
          model.newInstanceSpec.source = [ { id: 'cirros' } ];
          model.newInstanceSpec.flavor = { id: 'm1.tiny' };
          model.newInstanceSpec.networks = [ { id: 'public' }, { id: 'private' } ];
          model.newInstanceSpec.ports = [ ];
          model.newInstanceSpec.key_pair = [ { name: 'keypair1' } ];
          model.newInstanceSpec.security_groups = [ { id: 'adminId', name: 'admin' },
                                                    { id: 'demoId', name: 'demo' } ];
          model.newInstanceSpec.scheduler_hints = {};
          model.newInstanceSpec.vol_create = true;
          model.newInstanceSpec.vol_delete_on_instance_delete = true;
          model.newInstanceSpec.vol_size = 10;
          model.newInstanceSpec.server_groups = [];

          metadata = {'foo': 'bar'};
          model.metadataTree = {
            getExisting: function() {
              return metadata;
            }
          };

          hints = {'hint1': 'val1'};
          model.hintsTree = {
            getExisting: function() {
              return hints;
            }
          };
        });

        it('should set final spec in format required by Nova (Neutron disabled)', function() {
          var finalSpec = model.createInstance();
          var finalNetworks = [
            { 'net-id': 'public', 'v4-fixed-ip': '' },
            { 'net-id': 'private', 'v4-fixed-ip': '' }
          ];

          expect(finalSpec.source_id).toBe(null);
          expect(finalSpec.flavor_id).toBe('m1.tiny');
          expect(finalSpec.nics).toEqual(finalNetworks);
          expect(finalSpec.key_name).toBe('keypair1');
          expect(finalSpec.security_groups).toEqual([ 'admin', 'demo' ]);
        });

        it('should set final spec in format required for Block Device Mapping v2', function() {
          var finalSpec = model.createInstance();
          var expectedBlockDevice = [{
            source_type: 'image',
            destination_type: 'volume',
            delete_on_termination: true,
            uuid: 'cirros',
            boot_index: '0',
            volume_size: 10
          }];

          expect(finalSpec.block_device_mapping_v2).toEqual(expectedBlockDevice);
        });

        it('should set final security groups using name when Neutron enabled', function() {
          model.neutronEnabled = true;
          var finalSpec = model.createInstance();

          expect(finalSpec.security_groups).toEqual([ 'adminId', 'demoId' ]);
        });

        it('should remove flavor from final spec if flavor was null', function() {
          model.newInstanceSpec.flavor = null;

          var finalSpec = model.createInstance();
          expect(finalSpec.flavor_id).toBeUndefined();
        });

        it('should handle source type of "volume"', function() {
          model.newInstanceSpec.source_type.type = 'volume';
          model.newInstanceSpec.source[0].id = 'imAnID';
          model.newInstanceSpec.vol_delete_on_instance_delete = 'yep';

          var finalSpec = model.createInstance();
          expect(finalSpec.source_id).toBe('');
        });

        it('should handle source type of "snapshot"', function() {
          model.newInstanceSpec.source_type.type = 'snapshot';
          model.newInstanceSpec.source[0].id = 'imAnID';

          var finalSpec = model.createInstance();
          var expectedBlockDevice = [{
            source_type: 'image',
            destination_type: 'volume',
            delete_on_termination: true,
            uuid: 'imAnID',
            boot_index: '0',
            volume_size: 10
          }];

          expect(finalSpec.block_device_mapping_v2).toEqual(expectedBlockDevice);
        });

        it('should handle source type of "volume_snapshot"', function() {
          model.newInstanceSpec.source_type.type = 'volume_snapshot';
          model.newInstanceSpec.source[0].id = 'imAnID';
          model.newInstanceSpec.vol_delete_on_instance_delete = 'yep';

          var finalSpec = model.createInstance();
          expect(finalSpec.source_id).toBe('');
        });

        it('should process source_id if unknown type', function() {
          model.newInstanceSpec.source_type.type = 'unknown';
          model.newInstanceSpec.source[0].id = 'imAnID';

          var finalSpec = model.createInstance();
          expect(finalSpec.source_id).toBe('imAnID');
        });

        it('should not create block device mappings if not creating a volume', function() {
          model.newInstanceSpec.source_type.type = 'image';
          model.newInstanceSpec.vol_create = false;

          var finalSpec = model.createInstance();
          expect(finalSpec.block_device_mapping_v2).toBeUndefined();
        });

        it('sets a null key name & removes keypair if no key pair presented', function() {
          model.newInstanceSpec.key_pair = [];

          var finalSpec = model.createInstance();
          expect(finalSpec.key_name).toBeNull();
          expect(finalSpec.key_pair).toBeUndefined();
        });

        it('leaves the key name and removes keypair property if no key pair presented', function() {
          model.newInstanceSpec.key_pair = [];
          model.newInstanceSpec.key_name = 'Jerry';

          var finalSpec = model.createInstance();
          expect(finalSpec.key_name).toBe('Jerry');
          expect(finalSpec.key_pair).toBeUndefined();
        });

        it('strips null properties', function() {
          model.newInstanceSpec.useless = null;

          var finalSpec = model.createInstance();
          expect(finalSpec.useless).toBeUndefined();
        });

        it('should set final spec in format required if ports are used', function() {
          model.newInstanceSpec.ports = [{id: 'port1'}];

          var finalSpec = model.createInstance();
          var finalNetworks = [
            { 'net-id': 'public', 'v4-fixed-ip': '' },
            { 'net-id': 'private', 'v4-fixed-ip': '' },
            { 'port-id': 'port1' }
          ];

          expect(finalSpec.nics).toEqual(finalNetworks);
        });

        it('should not have meta property if no metadata specified', function() {
          metadata = {};

          var finalSpec = model.createInstance();
          expect(finalSpec.meta).toBeUndefined();

          model.metadataTree = null;

          finalSpec = model.createInstance();
          expect(finalSpec.meta).toBeUndefined();
        });

        it('should have meta property if metadata specified', function() {
          var finalSpec = model.createInstance();
          expect(finalSpec.meta).toBe(metadata);
        });

        it('should have only group for scheduler_hints if no other hints specified', function() {
          hints = {};
          model.newInstanceSpec.server_groups = [{'id': 'group1'}];
          var finalHints = {'group': model.newInstanceSpec.server_groups[0].id};

          var finalSpec = model.createInstance();
          expect(finalSpec.scheduler_hints).toEqual(finalHints);

          model.hintsTree = null;

          finalSpec = model.createInstance();
          expect(finalSpec.scheduler_hints).toEqual(finalHints);
        });

        it('should have scheduler_hints property if scheduler hints specified', function() {
          var finalHints = hints;
          finalHints.group = 'group1';

          var finalSpec = model.createInstance();
          expect(finalSpec.scheduler_hints).toEqual(finalHints);
        });

        it('should have no scheduler_hints if no scheduler hints specified', function() {
          hints = {};
          model.newInstanceSpec.server_groups = [];

          var finalSpec = model.createInstance();
          expect(finalSpec.scheduler_hints).toEqual({});
        });

      });
    });
  });
})();
