diff --git a/horizon/static/horizon/js/angular/services/hz.api.cinder.js b/horizon/static/horizon/js/angular/services/hz.api.cinder.js index 70fe02c190..50c1991bad 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.cinder.js +++ b/horizon/static/horizon/js/angular/services/hz.api.cinder.js @@ -44,7 +44,7 @@ limitations under the License. var config = (params) ? {'params': params} : {}; return apiService.get('/api/cinder/volumes/', config) .error(function () { - toastService.add('error', gettext('Unable to retrieve volumes.')); + toastService.add('error', gettext('Unable to retrieve the volumes.')); }); }; @@ -71,7 +71,7 @@ limitations under the License. return apiService.get('/api/cinder/volumesnapshots/', config) .error(function () { toastService.add('error', - gettext('Unable to retrieve volume snapshots.')); + gettext('Unable to retrieve the volume snapshots.')); }); }; } diff --git a/horizon/static/horizon/js/angular/services/hz.api.cinder.spec.js b/horizon/static/horizon/js/angular/services/hz.api.cinder.spec.js new file mode 100644 index 0000000000..1ef680bf5b --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.cinder.spec.js @@ -0,0 +1,75 @@ +/* + * (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('Cinder API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.cinder', function(cinderAPI) { + service = cinderAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + { func: 'getVolumes', + method: 'get', + path: '/api/cinder/volumes/', + data: { params: 'config' }, + error: 'Unable to retrieve the volumes.', + testInput: [ 'config' ] }, + + { func: 'getVolumes', + method: 'get', + path: '/api/cinder/volumes/', + data: {}, + error: 'Unable to retrieve the volumes.' }, + + { func: 'getVolumeSnapshots', + method: 'get', + path: '/api/cinder/volumesnapshots/', + data: {}, + error: 'Unable to retrieve the volume snapshots.' }, + + { func: 'getVolumeSnapshots', + method: 'get', + path: '/api/cinder/volumesnapshots/', + data: { params: 'config' }, + error: 'Unable to retrieve the volume snapshots.', + testInput: [ 'config' ] } ] ; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.name + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.common-test.spec.js b/horizon/static/horizon/js/angular/services/hz.api.common-test.spec.js new file mode 100644 index 0000000000..59b664a321 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.common-test.spec.js @@ -0,0 +1,83 @@ +/* + * (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'; + + /* This function tests the 'typical' way that apiService calls are made. + Look at this typical approach: + + this.getVolumes = function(params) { + var config = (params) ? {'params': params} : {}; + return apiService.get('/api/cinder/volumes/', config) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the volumes.')); + }); + + In this case there is an apiService call that is made with one or two + arguments, and on error a function is called that invokes the + toastService's add method. + + The function below takes in the set of variables, both input and output, + that are required to test the above code. It spies on the apiService + method that is called, and also spies on its returned simulated promise + 'error' method. + + Having established those spies, the code then inspects the parameters + passed to the apiService, as well as the results of those methods. + Then the code invokes of function passed to the error handler, and + ensures that the toastService is called with the appropriate parameters. + */ + function testCall(apiService, service, toastService, config) { + // 'promise' simulates a promise, including a self-referential success + // handler. + var promise = {error: angular.noop, success: function() { return this; }}; + spyOn(apiService, config.method).and.returnValue(promise); + spyOn(promise, 'error'); + service[config.func].apply(null, config.testInput); + + // Checks to ensure we call the api service with the appropriate + // parameters. + if (angular.isDefined(config.data)) { + expect(apiService[config.method]).toHaveBeenCalledWith(config.path, config.data); + } else { + expect(apiService[config.method]).toHaveBeenCalledWith(config.path); + } + + // The following retrieves the first argument of the first call to the + // error spy. This exposes the inner function that, when invoked, + // allows us to inspect the error call and the message given. + var innerFunc = promise.error.calls.argsFor(0)[0]; + expect(innerFunc).toBeDefined(); + spyOn(toastService, 'add'); + innerFunc(); + expect(toastService.add).toHaveBeenCalledWith(config.messageType || 'error', config.error); + } + + function initServices ($provide, apiService, toastService) { + angular.extend(apiService, { get: angular.noop, + post: angular.noop, + put: angular.noop, + patch: angular.noop, + delete: angular.noop }); + angular.extend(toastService, { add: angular.noop }); + $provide.value('hz.api.common.service', apiService); + $provide.value('horizon.framework.widgets.toast.service', toastService); + } + + window.apiTest = { testCall: testCall, initServices: initServices }; + +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.glance.js b/horizon/static/horizon/js/angular/services/hz.api.glance.js index c979e68c92..573791cda0 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.glance.js +++ b/horizon/static/horizon/js/angular/services/hz.api.glance.js @@ -35,7 +35,7 @@ limitations under the License. this.getImage = function(id) { return apiService.get('/api/glance/images/' + id) .error(function () { - toastService.add('error', gettext('Unable to retrieve image.')); + toastService.add('error', gettext('Unable to retrieve the image.')); }); }; @@ -78,7 +78,7 @@ limitations under the License. var config = (params) ? { 'params' : params} : {}; return apiService.get('/api/glance/images/', config) .error(function () { - toastService.add('error', gettext('Unable to retrieve images.')); + toastService.add('error', gettext('Unable to retrieve the images.')); }); }; @@ -141,7 +141,7 @@ limitations under the License. var promise = apiService.get('/api/glance/metadefs/namespaces/', config); return suppressError ? promise : promise.error(function() { - toastService.add('error', gettext('Unable to retrieve namespaces.')); + toastService.add('error', gettext('Unable to retrieve the namespaces.')); }); }; diff --git a/horizon/static/horizon/js/angular/services/hz.api.glance.spec.js b/horizon/static/horizon/js/angular/services/hz.api.glance.spec.js new file mode 100644 index 0000000000..be5c9f39c0 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.glance.spec.js @@ -0,0 +1,110 @@ +/* + * (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('Glance API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.glance', function(glanceAPI) { + service = glanceAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + { + "func": "getImage", + "method": "get", + "path": "/api/glance/images/42", + "error": "Unable to retrieve the image.", + "testInput": [ + 42 + ] + }, + { + "func": "getImages", + "method": "get", + "path": "/api/glance/images/", + "data": { + "params": "config" + }, + "error": "Unable to retrieve the images.", + "testInput": [ + "config" + ] + }, + { + "func": "getImages", + "method": "get", + "path": "/api/glance/images/", + "data": {}, + "error": "Unable to retrieve the images." + }, + { + "func": "getNamespaces", + "method": "get", + "path": "/api/glance/metadefs/namespaces/", + "data": { + "params": { + "orig": true + }, + "cache": true + }, + "error": "Unable to retrieve the namespaces.", + "testInput": [ + { + "orig": true + } + ] + }, + { + "func": "getNamespaces", + "method": "get", + "path": "/api/glance/metadefs/namespaces/", + "data": { + "cache": true + }, + "error": "Unable to retrieve the namespaces." + } + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + it('supresses the error if instructed for getNamespaces', function() { + spyOn(apiService, 'get').and.returnValue("promise"); + expect(service.getNamespaces("whatever", true)).toBe("promise"); + }); + + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.keystone.js b/horizon/static/horizon/js/angular/services/hz.api.keystone.js index b8555c37a9..4edfcba2e3 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.keystone.js +++ b/horizon/static/horizon/js/angular/services/hz.api.keystone.js @@ -21,7 +21,7 @@ limitations under the License. var config = (params) ? {'params': params} : {}; return apiService.get('/api/keystone/users/', config) .error(function () { - toastService.add('error', gettext('Unable to retrieve users')); + toastService.add('error', gettext('Unable to retrieve users.')); }); }; @@ -77,7 +77,7 @@ limitations under the License. this.getUser = function(user_id) { return apiService.get('/api/keystone/users/' + user_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve the user')); + toastService.add('error', gettext('Unable to retrieve the user.')); }); }; @@ -100,7 +100,7 @@ limitations under the License. this.getRoles = function() { return apiService.get('/api/keystone/roles/') .error(function () { - toastService.add('error', gettext('Unable to retrieve role')); + toastService.add('error', gettext('Unable to retrieve the roles.')); }); }; @@ -121,7 +121,7 @@ limitations under the License. this.getRole = function(role_id) { return apiService.get('/api/keystone/roles/' + role_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve the role')); + toastService.add('error', gettext('Unable to retrieve the role.')); }); }; @@ -144,7 +144,7 @@ limitations under the License. this.getDomains = function() { return apiService.get('/api/keystone/domains/') .error(function () { - toastService.add('error', gettext('Unable to retrieve domains')); + toastService.add('error', gettext('Unable to retrieve the domains.')); }); }; @@ -165,7 +165,7 @@ limitations under the License. this.getDomain = function(domain_id) { return apiService.get('/api/keystone/domains/' + domain_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve the domain')); + toastService.add('error', gettext('Unable to retrieve the domain.')); }); }; @@ -189,7 +189,7 @@ limitations under the License. var config = (params) ? {'params': params} : {}; return apiService.get('/api/keystone/projects/', config) .error(function () { - toastService.add('error', gettext('Unable to retrieve projects')); + toastService.add('error', gettext('Unable to retrieve the projects.')); }); }; @@ -210,7 +210,7 @@ limitations under the License. this.getProject = function(project_id) { return apiService.get('/api/keystone/projects/' + project_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve the project')); + toastService.add('error', gettext('Unable to retrieve the project.')); }); }; @@ -230,7 +230,7 @@ limitations under the License. }; this.grantRole = function(project_id, role_id, user_id) { - return apiService.delete('/api/keystone/projects/' + project_id + '/' + + return apiService.put('/api/keystone/projects/' + project_id + '/' + role_id + '/' + user_id) .error(function () { toastService.add('error', gettext('Unable to grant the role.')); diff --git a/horizon/static/horizon/js/angular/services/hz.api.keystone.spec.js b/horizon/static/horizon/js/angular/services/hz.api.keystone.spec.js new file mode 100644 index 0000000000..b24f5b7707 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.keystone.spec.js @@ -0,0 +1,557 @@ +/* + * (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('Keystone API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.keystone', function(keystoneAPI) { + service = keystoneAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + { + "func": "getUsers", + "method": "get", + "path": "/api/keystone/users/", + "data": {}, + "error": "Unable to retrieve users." + }, + { + "func": "getUsers", + "method": "get", + "path": "/api/keystone/users/", + "data": { + "params": { + "info": true + } + }, + "error": "Unable to retrieve users.", + "testInput": [ + { + "info": true + } + ] + }, + { + "func": "createUser", + "method": "post", + "path": "/api/keystone/users/", + "data": { + "name": "Matt" + }, + "error": "Unable to create the user.", + "testInput": [ + { + "name": "Matt" + } + ] + }, + { + "func": "deleteUsers", + "method": "delete", + "path": "/api/keystone/users/", + "data": [ + 1, + 2, + 3 + ], + "error": "Unable to delete the users.", + "testInput": [ + [ + 1, + 2, + 3 + ] + ] + }, + { + "func": "getCurrentUserSession", + "method": "get", + "path": "/api/keystone/user-session/", + "data": "config", + "error": "Unable to retrieve the current user session.", + "testInput": [ + "config" + ] + }, + { + "func": "getUser", + "method": "get", + "path": "/api/keystone/users/42", + "error": "Unable to retrieve the user.", + "testInput": [ + 42 + ] + }, + { + "func": "editUser", + "method": "patch", + "path": "/api/keystone/users/42", + "data": { + "id": 42 + }, + "error": "Unable to edit the user.", + "testInput": [ + { + "id": 42 + } + ] + }, + { + "func": "deleteUser", + "method": "delete", + "path": "/api/keystone/users/42", + "error": "Unable to delete the user.", + "testInput": [ + 42 + ] + }, + { + "func": "getRoles", + "method": "get", + "path": "/api/keystone/roles/", + "error": "Unable to retrieve the roles." + }, + { + "func": "createRole", + "method": "post", + "path": "/api/keystone/roles/", + "data": "new role", + "error": "Unable to create the role.", + "testInput": [ + "new role" + ] + }, + { + "func": "deleteRoles", + "method": "delete", + "path": "/api/keystone/roles/", + "data": [ + 1, + 2, + 3 + ], + "error": "Unable to delete the roles.", + "testInput": [ + [ + 1, + 2, + 3 + ] + ] + }, + { + "func": "getRole", + "method": "get", + "path": "/api/keystone/roles/42", + "error": "Unable to retrieve the role.", + "testInput": [ + 42 + ] + }, + { + "func": "editRole", + "method": "patch", + "path": "/api/keystone/roles/42", + "data": { + "id": 42 + }, + "error": "Unable to edit the role.", + "testInput": [ + { + "id": 42 + } + ] + }, + { + "func": "deleteRole", + "method": "delete", + "path": "/api/keystone/roles/42", + "error": "Unable to delete the role.", + "testInput": [ + 42 + ] + }, + { + "func": "getDomains", + "method": "get", + "path": "/api/keystone/domains/", + "error": "Unable to retrieve the domains." + }, + { + "func": "createDomain", + "method": "post", + "path": "/api/keystone/domains/", + "data": "new domain", + "error": "Unable to create the domain.", + "testInput": [ + "new domain" + ] + }, + { + "func": "deleteDomains", + "method": "delete", + "path": "/api/keystone/domains/", + "data": [ + 1, + 2, + 3 + ], + "error": "Unable to delete the domains.", + "testInput": [ + [ + 1, + 2, + 3 + ] + ] + }, + { + "func": "getDomain", + "method": "get", + "path": "/api/keystone/domains/42", + "error": "Unable to retrieve the domain.", + "testInput": [ + 42 + ] + }, + { + "func": "editDomain", + "method": "patch", + "path": "/api/keystone/domains/42", + "data": { + "id": 42 + }, + "error": "Unable to edit the domain.", + "testInput": [ + { + "id": 42 + } + ] + }, + { + "func": "deleteDomain", + "method": "delete", + "path": "/api/keystone/domains/42", + "error": "Unable to delete the domain.", + "testInput": [ + 42 + ] + }, + { + "func": "getProjects", + "method": "get", + "path": "/api/keystone/projects/", + "data": {}, + "error": "Unable to retrieve the projects." + }, + { + "func": "getProjects", + "method": "get", + "path": "/api/keystone/projects/", + "data": { + "params": { + "info": true + } + }, + "error": "Unable to retrieve the projects.", + "testInput": [ + { + "info": true + } + ] + }, + { + "func": "createProject", + "method": "post", + "path": "/api/keystone/projects/", + "data": "new project", + "error": "Unable to create the project.", + "testInput": [ + "new project" + ] + }, + { + "func": "deleteProjects", + "method": "delete", + "path": "/api/keystone/projects/", + "data": [ + 1, + 2, + 3 + ], + "error": "Unable to delete the projects.", + "testInput": [ + [ + 1, + 2, + 3 + ] + ] + }, + { + "func": "getProject", + "method": "get", + "path": "/api/keystone/projects/42", + "error": "Unable to retrieve the project.", + "testInput": [ + 42 + ] + }, + { + "func": "editProject", + "method": "patch", + "path": "/api/keystone/projects/42", + "data": { + "id": 42 + }, + "error": "Unable to edit the project.", + "testInput": [ + { + "id": 42 + } + ] + }, + { + "func": "deleteProject", + "method": "delete", + "path": "/api/keystone/projects/42", + "error": "Unable to delete the project.", + "testInput": [ + 42 + ] + }, + { + "func": "grantRole", + "method": "put", + "path": "/api/keystone/projects/42/32/22", + "error": "Unable to grant the role.", + "testInput": [ + 42, + 32, + 22 + ] + }, + { + "func": "serviceCatalog", + "method": "get", + "path": "/api/keystone/svc-catalog/", + "data": "config", + "error": "Unable to fetch the service catalog.", + "testInput": [ + "config" + ] + } + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + }); + + describe("userSession", function() { + var factory, keystoneAPI; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + keystoneAPI = {getCurrentUserSession: angular.noop}; + $provide.value('hz.api.keystone', keystoneAPI); + $provide.value('$cacheFactory', function() { return 'cache'; }); + })); + + beforeEach(inject(['hz.api.userSession', function(userSession) { + factory = userSession; + }])); + + it('defines the factory', function() { + expect(factory).toBeDefined(); + }); + + it('defines .cache', function() { + expect(factory.cache).toBe("cache"); + }); + + it('defines .get', function() { + expect(factory.get).toBeDefined(); + }); + + describe(".get() features", function() { + var postAction = {then: angular.noop}; + + beforeEach(function() { + spyOn(keystoneAPI, 'getCurrentUserSession').and.returnValue(postAction); + spyOn(postAction, 'then'); + factory.get(); + }); + + it("calls getCurrentUserSession", function() { + expect(keystoneAPI.getCurrentUserSession).toHaveBeenCalled(); + }); + + it("then returns the response's data member", function() { + var func = postAction.then.calls.argsFor(0)[0]; + expect(func({data: 'thing'})).toBe("thing"); + }); + }); + + }); + + describe("serviceCatalog", function() { + var factory, q, keystoneAPI, userSession, deferred; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + keystoneAPI = {serviceCatalog: angular.noop}; + $provide.value('hz.api.keystone', keystoneAPI); + userSession = {get: angular.noop}; + $provide.value('hz.api.userSession', userSession); + deferred = {promise: angular.noop, reject: angular.noop, resolve: angular.noop}; + q = {all: function() {return {then: angular.noop};}, + defer: function() { return deferred;}}; + $provide.value('$q', q); + $provide.value('$cacheFactory', function() { return 'cache'; }); + })); + + beforeEach(inject(['hz.api.serviceCatalog', function(serviceCatalog) { + factory = serviceCatalog; + }])); + + it('defines the factory', function() { + expect(factory).toBeDefined(); + }); + + it('defines .cache', function() { + expect(factory.cache).toBe("cache"); + }); + + it('defines .get', function() { + expect(factory.get).toBeDefined(); + }); + + describe(".get() features", function() { + var postAction = {then: angular.noop}; + + beforeEach(function() { + spyOn(keystoneAPI, 'serviceCatalog').and.returnValue(postAction); + spyOn(postAction, 'then'); + factory.get(); + }); + + it("gets the service catalog", function() { + expect(keystoneAPI.serviceCatalog).toHaveBeenCalled(); + }); + + it("then returns the response's data member", function() { + var func = postAction.then.calls.argsFor(0)[0]; + expect(func({data: 'thing'})).toBe("thing"); + }); + + }); + + it('defines .ifTypeEnabled', function() { + expect(factory.ifTypeEnabled).toBeDefined(); + }); + + describe(".ifTypeEnabled features", function() { + var postAction = {then: angular.noop}; + + beforeEach(function() { + spyOn(q, 'all').and.returnValue(postAction); + spyOn(factory, 'get'); + spyOn(postAction, 'then'); + spyOn(deferred, 'reject'); + spyOn(deferred, 'resolve'); + }); + + var callMethod = function(type, data, resolved) { + factory.ifTypeEnabled(type); + expect(q.all).toHaveBeenCalled(); + + var successFunc = postAction.then.calls.argsFor(0)[0]; + var failFunc = postAction.then.calls.argsFor(0)[1]; + successFunc(data); + + // If we expected this to be resolved, then expect + // both that we did call resolve() and did NOT call reject(). + // Vice versa if expecting rejection. + if (resolved) { + expect(deferred.resolve).toHaveBeenCalled(); + expect(deferred.reject).not.toHaveBeenCalled(); + } else { + expect(deferred.resolve).not.toHaveBeenCalled(); + expect(deferred.reject).toHaveBeenCalled(); + } + + deferred.reject.calls.reset(); + failFunc(); + expect(deferred.reject).toHaveBeenCalled(); + }; + + it("accepts 'desired' type with no matches; is rejected", function() { + var data = {catalog: [1, 2, 3], + session: {services_region: true}}; + callMethod("desired", data, false); + }); + + it("accepts 'desired' type with matches but no endpoints; is rejected", function() { + var data = {catalog: [1, {type: "desired", endpoints: []}, 3], + session: {services_region: true}}; + callMethod("desired", data, false); + }); + + it("accepts 'desired' type with a match; is resolved", function() { + var data = {catalog: [{type: "desired", endpoints: [{region_id: true}]}], + session: {services_region: true}}; + callMethod("desired", data, true); + }); + + it("accepts 'desired' type with matches and region is true; is resolved", function() { + var data = {catalog: [{type: "desired", endpoints: [{region: true}]}], + session: {services_region: true}}; + callMethod("desired", data, true); + }); + + it("accepts 'desired' type with matches and service_region is true; is resolved", function() { + var data = {catalog: [{type: "identity"}], + session: {services_region: true}}; + callMethod("identity", data, true); + }); + }); + + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.neutron.js b/horizon/static/horizon/js/angular/services/hz.api.neutron.js index f168a0e0ac..0cbbe92abd 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.neutron.js +++ b/horizon/static/horizon/js/angular/services/hz.api.neutron.js @@ -36,7 +36,7 @@ this.getNetworks = function() { return apiService.get('/api/neutron/networks/') .error(function () { - toastService.add('error', gettext('Unable to retrieve networks.')); + toastService.add('error', gettext('Unable to retrieve the networks.')); }); }; @@ -105,7 +105,7 @@ this.getSubnets = function(network_id) { return apiService.get('/api/neutron/subnets/', network_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve subnets.')); + toastService.add('error', gettext('Unable to retrieve the subnets.')); }); }; @@ -189,7 +189,7 @@ this.getPorts = function(network_id) { return apiService.get('/api/neutron/ports/', network_id) .error(function () { - toastService.add('error', gettext('Unable to retrieve ports.')); + toastService.add('error', gettext('Unable to retrieve the ports.')); }); }; diff --git a/horizon/static/horizon/js/angular/services/hz.api.neutron.spec.js b/horizon/static/horizon/js/angular/services/hz.api.neutron.spec.js new file mode 100644 index 0000000000..bee55e13f0 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.neutron.spec.js @@ -0,0 +1,99 @@ +/* + * (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('Neutron API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.neutron', function(neutronAPI) { + service = neutronAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + + { + "func": "getNetworks", + "method": "get", + "path": "/api/neutron/networks/", + "error": "Unable to retrieve the networks." + }, + { + "func": "createNetwork", + "method": "post", + "path": "/api/neutron/networks/", + "data": "new net", + "error": "Unable to create the network.", + "testInput": [ + "new net" + ] + }, + { + "func": "getSubnets", + "method": "get", + "path": "/api/neutron/subnets/", + "data": 42, + "error": "Unable to retrieve the subnets.", + "testInput": [ + 42 + ] + }, + { + "func": "createSubnet", + "method": "post", + "path": "/api/neutron/subnets/", + "data": "new subnet", + "error": "Unable to create the subnet.", + "testInput": [ + "new subnet" + ] + }, + { + "func": "getPorts", + "method": "get", + "path": "/api/neutron/ports/", + "data": 42, + "error": "Unable to retrieve the ports.", + "testInput": [ + 42 + ] + } + + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.nova.js b/horizon/static/horizon/js/angular/services/hz.api.nova.js index 11ea687021..76fa328998 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.nova.js +++ b/horizon/static/horizon/js/angular/services/hz.api.nova.js @@ -36,7 +36,7 @@ limitations under the License. this.getKeypairs = function() { return apiService.get('/api/nova/keypairs/') .error(function () { - toastService.add('error', gettext('Unable to retrieve keypairs.')); + toastService.add('error', gettext('Unable to retrieve the keypairs.')); }); }; @@ -79,7 +79,7 @@ limitations under the License. return apiService.get('/api/nova/availzones/') .error(function () { toastService.add('error', - gettext('Unable to retrieve availability zones.')); + gettext('Unable to retrieve the availability zones.')); }); }; @@ -117,7 +117,7 @@ limitations under the License. this.getLimits = function() { return apiService.get('/api/nova/limits/') .error(function () { - toastService.add('error', gettext('Unable to retrieve limits.')); + toastService.add('error', gettext('Unable to retrieve the limits.')); }); }; @@ -158,7 +158,7 @@ limitations under the License. this.getServer = function(id) { return apiService.get('/api/nova/servers/' + id) .error(function () { - toastService.add('error', gettext('Unable to retrieve server.')); + toastService.add('error', gettext('Unable to retrieve the server.')); }); }; @@ -188,7 +188,7 @@ limitations under the License. this.getExtensions = function(config) { return apiService.get('/api/nova/extensions/', config) .error(function () { - toastService.add('error', gettext('Unable to retrieve extensions.')); + toastService.add('error', gettext('Unable to retrieve the extensions.')); }); }; @@ -233,7 +233,7 @@ limitations under the License. } }) .error(function () { - toastService.add('error', gettext('Unable to retrieve flavors.')); + toastService.add('error', gettext('Unable to retrieve the flavors.')); }); }; @@ -251,7 +251,7 @@ limitations under the License. if (getExtras) { config.params.get_extras = 'true'; } return apiService.get('/api/nova/flavors/' + id, config) .error(function () { - toastService.add('error', gettext('Unable to retrieve flavor.')); + toastService.add('error', gettext('Unable to retrieve the flavor.')); }); }; @@ -265,7 +265,7 @@ limitations under the License. this.getFlavorExtraSpecs = function(id) { return apiService.get('/api/nova/flavors/' + id + '/extra-specs') .error(function () { - toastService.add('error', gettext('Unable to retrieve flavor extra specs.')); + toastService.add('error', gettext('Unable to retrieve the flavor extra specs.')); }); }; } @@ -314,7 +314,7 @@ limitations under the License. } function onDataFailure() { - deferred.reject(gettext('Cannot get nova extension list.')); + deferred.reject(gettext('Cannot get Nova extension list.')); } return deferred.promise; diff --git a/horizon/static/horizon/js/angular/services/hz.api.nova.spec.js b/horizon/static/horizon/js/angular/services/hz.api.nova.spec.js new file mode 100644 index 0000000000..698b2de633 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.nova.spec.js @@ -0,0 +1,314 @@ +/* + * (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('Nova API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.nova', function(novaAPI) { + service = novaAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + { + "func": "getKeypairs", + "method": "get", + "path": "/api/nova/keypairs/", + "error": "Unable to retrieve the keypairs." + }, + { + "func": "createKeypair", + "method": "post", + "path": "/api/nova/keypairs/", + "data": { + "public_key": true + }, + "error": "Unable to import the keypair.", + "testInput": [ + { + "public_key": true + } + ] + }, + { + "func": "createKeypair", + "method": "post", + "path": "/api/nova/keypairs/", + "data": {}, + "error": "Unable to create the keypair.", + "testInput": [ + {} + ] + }, + { + "func": "getAvailabilityZones", + "method": "get", + "path": "/api/nova/availzones/", + "error": "Unable to retrieve the availability zones." + }, + { + "func": "getLimits", + "method": "get", + "path": "/api/nova/limits/", + "error": "Unable to retrieve the limits." + }, + { + "func": "createServer", + "method": "post", + "path": "/api/nova/servers/", + "data": "new server", + "error": "Unable to create the server.", + "testInput": [ + "new server" + ] + }, + { + "func": "getServer", + "method": "get", + "path": "/api/nova/servers/42", + "error": "Unable to retrieve the server.", + "testInput": [ + 42 + ] + }, + { + "func": "getExtensions", + "method": "get", + "path": "/api/nova/extensions/", + "data": "config", + "error": "Unable to retrieve the extensions.", + "testInput": [ + "config" + ] + }, + { + "func": "getFlavors", + "method": "get", + "path": "/api/nova/flavors/", + "data": { + "params": {} + }, + "error": "Unable to retrieve the flavors.", + "testInput": [ + false, + false + ] + }, + { + "func": "getFlavors", + "method": "get", + "path": "/api/nova/flavors/", + "data": { + "params": { + "is_public": "true" + } + }, + "error": "Unable to retrieve the flavors.", + "testInput": [ + true, + false + ] + }, + { + "func": "getFlavors", + "method": "get", + "path": "/api/nova/flavors/", + "data": { + "params": { + "get_extras": "true" + } + }, + "error": "Unable to retrieve the flavors.", + "testInput": [ + false, + true + ] + }, + { + "func": "getFlavors", + "method": "get", + "path": "/api/nova/flavors/", + "data": { + "params": { + "is_public": "true", + "get_extras": "true" + } + }, + "error": "Unable to retrieve the flavors.", + "testInput": [ + true, + true + ] + }, + { + "func": "getFlavor", + "method": "get", + "path": "/api/nova/flavors/42", + "data": { + "params": { + "get_extras": "true" + } + }, + "error": "Unable to retrieve the flavor.", + "testInput": [ + 42, + true + ] + }, + { + "func": "getFlavor", + "method": "get", + "path": "/api/nova/flavors/42", + "data": { + "params": {} + }, + "error": "Unable to retrieve the flavor.", + "testInput": [ + 42, + false + ] + }, + { + "func": "getFlavorExtraSpecs", + "method": "get", + "path": "/api/nova/flavors/42/extra-specs", + "error": "Unable to retrieve the flavor extra specs.", + "testInput": [ + 42 + ] + } + + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + it('getFlavors converts specific property names with : in them', function() { + var postAction = {success: angular.noop}; + spyOn(apiService, 'get').and.returnValue(postAction); + spyOn(postAction, 'success').and.returnValue({error: angular.noop}); + service.getFlavors(); + var func = postAction.success.calls.argsFor(0)[0]; + + // won't do anything. Need to test that it won't do anything. + func(); + + var data = {items: [{nada: 'puesNada'}]}; + func(data); + expect(data).toEqual({items: [{nada: 'puesNada'}]}); + + data = {items: [{'OS-FLV-EXT-DATA:ephemeral': true}]}; + func(data); + expect(data).toEqual({items: [{'OS-FLV-EXT-DATA:ephemeral': true, ephemeral: true}]}); + + data = {items: [{'OS-FLV-DISABLED:disabled': true}]}; + func(data); + expect(data).toEqual({items: [{'OS-FLV-DISABLED:disabled': true, disabled: true}]}); + + data = {items: [{'os-flavor-access:is_public': true}]}; + func(data); + expect(data).toEqual({items: [{'os-flavor-access:is_public': true, is_public: true}]}); + + }); + }); + + describe("novaExtensions", function() { + var factory, q, novaAPI; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + novaAPI = {getExtensions: function() {return {then: angular.noop};}}; + q = {defer: function() { return {resolve: angular.noop}; }}; + $provide.value('$cacheFactory', function() {return "cache";}); + $provide.value('$q', q); + $provide.value('hz.api.nova', novaAPI); + })); + + beforeEach(inject(function($injector) { + factory = $injector.get('hz.api.novaExtensions'); + })); + + it("is defined", function() { + expect(factory).toBeDefined(); + }); + + it("defines .cache", function() { + expect(factory.cache).toBeDefined(); + }); + + it("defines .get", function() { + expect(factory.get).toBeDefined(); + var postAction = {then: angular.noop}; + spyOn(novaAPI, 'getExtensions').and.returnValue(postAction); + spyOn(postAction, 'then'); + factory.get(); + expect(novaAPI.getExtensions).toHaveBeenCalledWith({cache: factory.cache}); + expect(postAction.then).toHaveBeenCalled(); + var func = postAction.then.calls.argsFor(0)[0]; + var testData = {data: {items: [1, 2, 3]}}; + expect(func(testData)).toEqual([1, 2, 3]); + }); + + it("defines .ifNameEnabled", function() { + expect(factory.ifNameEnabled).toBeDefined(); + var postAction = {then: angular.noop}; + var deferred = {reject: angular.noop, resolve: angular.noop}; + spyOn(q, 'defer').and.returnValue(deferred); + spyOn(factory, 'get').and.returnValue(postAction); + spyOn(postAction, 'then'); + factory.ifNameEnabled("desired"); + expect(factory.get).toHaveBeenCalled(); + var func1 = postAction.then.calls.argsFor(0)[0]; + var func2 = postAction.then.calls.argsFor(0)[1]; + spyOn(deferred, 'reject'); + func1(); + expect(deferred.reject).toHaveBeenCalled(); + + spyOn(deferred, 'resolve'); + var extensions = [{name: "desired"}]; + func1(extensions); + expect(deferred.resolve).toHaveBeenCalled(); + + deferred.reject.calls.reset(); + func2(); + expect(deferred.reject).toHaveBeenCalledWith('Cannot get Nova extension list.'); + }); + + it("defines .ifEnabled", function() { + expect(factory.ifEnabled).toBeDefined(); + }); + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.policy.spec.js b/horizon/static/horizon/js/angular/services/hz.api.policy.spec.js new file mode 100644 index 0000000000..b262606431 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.policy.spec.js @@ -0,0 +1,62 @@ +/* + * (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('Policy API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.policy', function(policyAPI) { + service = policyAPI; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + { + "func": "check", + "method": "post", + "path": "/api/policy/", + "data": "rules", + "error": "Policy check failed.", + "testInput": [ + "rules" + ], + "messageType": "warning" + } + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + }); +})(); diff --git a/horizon/static/horizon/js/angular/services/hz.api.security-group.js b/horizon/static/horizon/js/angular/services/hz.api.security-group.js index 2b710bd002..fe9f8f2285 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.security-group.js +++ b/horizon/static/horizon/js/angular/services/hz.api.security-group.js @@ -61,7 +61,7 @@ limitations under the License. this.query = function() { return apiService.get('/api/network/securitygroups/') .error(function () { - toastService.add('error', gettext('Unable to retrieve security groups.')); + toastService.add('error', gettext('Unable to retrieve the security groups.')); }); }; } diff --git a/horizon/static/horizon/js/angular/services/hz.api.security-group.spec.js b/horizon/static/horizon/js/angular/services/hz.api.security-group.spec.js new file mode 100644 index 0000000000..585d27c24f --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.security-group.spec.js @@ -0,0 +1,59 @@ +/* + * (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('Security Group API', function() { + var service; + var apiService = {}; + var toastService = {}; + + beforeEach(module('hz.api')); + + beforeEach(module(function($provide) { + window.apiTest.initServices($provide, apiService, toastService); + })); + + beforeEach(inject(['hz.api.security-group', function(securityGroup) { + service = securityGroup; + }])); + + it('defines the service', function() { + expect(service).toBeDefined(); + }); + + var tests = [ + + { + "func": "query", + "method": "get", + "path": "/api/network/securitygroups/", + "error": "Unable to retrieve the security groups." + } + + ]; + + // Iterate through the defined tests and apply as Jasmine specs. + angular.forEach(tests, function(params) { + it('defines the ' + params.func + ' call properly', function() { + var callParams = [apiService, service, toastService, params]; + window.apiTest.testCall.apply(this, callParams); + }); + }); + + }); +})(); diff --git a/horizon/test/jasmine/jasmine_tests.py b/horizon/test/jasmine/jasmine_tests.py index 10b6495580..ceec1d0298 100644 --- a/horizon/test/jasmine/jasmine_tests.py +++ b/horizon/test/jasmine/jasmine_tests.py @@ -19,7 +19,15 @@ class ServicesTests(test.JasmineTests): 'horizon/js/horizon.js', 'horizon/js/angular/hz.api.module.js', 'horizon/js/angular/services/hz.api.common.js', + 'horizon/js/angular/services/hz.api.service.js', + 'horizon/js/angular/services/hz.api.cinder.js', 'horizon/js/angular/services/hz.api.config.js', + 'horizon/js/angular/services/hz.api.glance.js', + 'horizon/js/angular/services/hz.api.keystone.js', + 'horizon/js/angular/services/hz.api.neutron.js', + 'horizon/js/angular/services/hz.api.nova.js', + 'horizon/js/angular/services/hz.api.policy.js', + 'horizon/js/angular/services/hz.api.security-group.js', 'dashboard-app/dashboard-app.module.js', 'dashboard-app/login/login.js', @@ -60,10 +68,18 @@ class ServicesTests(test.JasmineTests): ] specs = [ 'horizon/js/angular/services/hz.api.common.spec.js', + 'horizon/js/angular/services/hz.api.common-test.spec.js', + 'horizon/js/angular/services/hz.api.cinder.spec.js', 'horizon/js/angular/services/hz.api.config.spec.js', - 'dashboard-app/login/login.spec.js', 'dashboard-app/utils/helper-functions.spec.js', + 'horizon/js/angular/services/hz.api.glance.spec.js', + 'horizon/js/angular/services/hz.api.keystone.spec.js', + 'horizon/js/angular/services/hz.api.neutron.spec.js', + 'horizon/js/angular/services/hz.api.nova.spec.js', + 'horizon/js/angular/services/hz.api.policy.spec.js', + 'horizon/js/angular/services/hz.api.security-group.spec.js', + 'horizon/tests/jasmine/utils.spec.js', 'framework/util/bind-scope/bind-scope.spec.js', 'framework/util/filters/filters.spec.js',