Add Angular keystone user creation action

- fix users rest API to handle description,
  project, and enabled attribute properly
- fix hz-password-match to work, but also so
  it uses model-based checks rather than HTML
  element lookups
- add schema form password-confirm type

To Test
 - set 'users_panel': True in settings.py

Co-Authored-By: Cindy Lu <clu@us.ibm.com>
Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com>
Co-Authored-By: Shu Muto <shu-mutou@rf.jp.nec.com>

Change-Id: I2e8f5ac1dbfae9634fa5b72f7f6c0278d5a81249
Partially-Implements: blueprint ng-users
This commit is contained in:
jing.liuqing 2016-07-14 10:51:20 +08:00 committed by Shu Muto
parent e23048774b
commit 4c8f4a0306
20 changed files with 658 additions and 46 deletions

View File

@ -31,49 +31,42 @@
* @restrict A
*
* @scope
* hzPasswordMatch - id of element to validate against
* hzPasswordMatch - model value to validate against
*
* @example:
* <form name="form">
* <input type='password' id="psw" ng-model="user.psw" name="psw">
* <input type='password' ng-model="user.cnf" hz-password-match="psw">
* <input type='password' ng-model="user.cnf" hz-password-match="user.psw">
* </form>
*
* Note that id and name are required for the password input.
* This directive uses the form model and id for validation check.
*/
angular
.module('horizon.framework.util.validators')
.directive('hzPasswordMatch', hzPasswordMatch);
function hzPasswordMatch() {
var directive = {
return {
restrict: 'A',
require: 'ngModel',
link: link
};
return directive;
///////////
function link(scope, element, attr, ctrl) {
function link(scope, element, attr, ngModelCtrl) {
/**
* this ensures that typing in either input
* will trigger the password match
*/
var pwElement = angular.element('#' + attr.hzPasswordMatch);
pwElement.on('keyup change', passwordCheck);
element.on('keyup change', passwordCheck);
scope.$watch(attr.hzPasswordMatch, function(value) {
scope.passwordConfirm = value;
ngModelCtrl.$validate();
});
// helper function to check that password matches
function passwordCheck() {
scope.$apply(function () {
var match = ctrl.$modelValue === pwElement.val();
ctrl.$setValidity('match', match);
});
}
ngModelCtrl.$validators.check = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return value === scope.passwordConfirm;
};
} // end of link
} // end of directive
})();

View File

@ -23,7 +23,7 @@
var markup =
'<form>' +
'<input type="password" ng-model="user.password" id="password" name="password">' +
'<input type="password" ng-model="user.cpassword" hz-password-match="password">' +
'<input type="password" ng-model="user.cpassword" hz-password-match="user.password">' +
'</form>';
beforeEach(module('horizon.framework.widgets'));

View File

@ -117,6 +117,10 @@
template: base + 'radio-buttons.html',
builder: defaults
},
'password-confirm': {
template: base + 'password-confirm.html',
builder: defaults
},
help: {
template: base + 'help.html',
builder: defaults

View File

@ -49,7 +49,7 @@
'__name', 'textarea', 'fieldset', 'array', 'tabarray', 'tabs', 'section',
'conditional', 'select', 'checkbox', 'checkboxes', 'number',
'password', 'submit', 'button', 'radios', 'radios-inline', 'radiobuttons',
'help', 'default'
'password-confirm', 'help', 'default'
];
expect(fields).toEqual(expectedFields);
});

View File

@ -0,0 +1,57 @@
<div class="form-group hz-input {$::form.htmlClass$}"
ng-class="{'has-error': form.disableErrorState !== true && hasError(), 'has-success': form.disableSuccessState !== true && hasSuccess(), 'has-feedback': form.feedback !== false }">
<label class="control-label {$::form.labelHtmlClass$}" ng-class="{'sr-only': !showTitle()}" for="{$::form.key.slice(-1)[0]$}">
<span>{$::form.title$}</span>
<span ng-if="form.required" class="hz-icon-required fa fa-asterisk"></span>
</label>
<input ng-if="!form.fieldAddonLeft && !form.fieldAddonRight"
ng-show="form.key"
type="password"
step="any"
sf-changed="form"
placeholder="{$::form.placeholder$}"
class="form-control {$::form.fieldHtmlClass$}"
id="{$::form.key.slice(-1)[0]$}"
sf-field-model
ng-disabled="form.readonly"
schema-validate="form"
name="{$::form.key.slice(-1)[0]$}"
aria-describedby="{$::form.key.slice(-1)[0] + 'Status'$}"
hz-password-match="{$::form.match$}">
<div ng-if="form.fieldAddonLeft || form.fieldAddonRight"
ng-class="{'input-group': (form.fieldAddonLeft || form.fieldAddonRight)}">
<span ng-if="form.fieldAddonLeft"
class="input-group-addon"
ng-bind-html="form.fieldAddonLeft"></span>
<input ng-show="form.key"
type="password"
step="any"
sf-changed="form"
placeholder="{$::form.placeholder$}"
class="form-control {$::form.fieldHtmlClass$}"
id="{$::form.key.slice(-1)[0]$}"
sf-field-model
ng-disabled="form.readonly"
schema-validate="form"
name="{$::form.key.slice(-1)[0]$}"
aria-describedby="{$::form.key.slice(-1)[0] + 'Status'$}"
hz-password-match="{$::form.match$}">
<span ng-if="form.fieldAddonRight"
class="input-group-addon"
ng-bind-html="form.fieldAddonRight"></span>
</div>
<span ng-if="form.feedback !== false"
class="form-control-feedback"
ng-class="evalInScope(form.feedback) || {'fa': true, 'fa-check': hasSuccess(), 'fa-times': hasError() }"
aria-hidden="true"></span>
<span ng-if="hasError() || hasSuccess()"
id="{$::form.key.slice(-1)[0] + 'Status'$}"
class="sr-only">{$ hasSuccess() ? '(success)' : '(error)' $}</span>
<div class="help-block" sf-message="form.description"></div>
</div>

View File

@ -71,20 +71,22 @@ class Users(generic.View):
Create a user using the parameters supplied in the POST
application/json object. The base parameters are name (string), email
(string, optional), password (string, optional), project_id (string,
optional), enabled (boolean, defaults to true). The user will be
created in the default domain.
(string, optional), password (string), project (string,
optional), enabled (boolean, defaults to true), description
(string, optional). The user will be created in the default domain.
This action returns the new user object on success.
"""
domain = api.keystone.get_default_domain(request)
new_user = api.keystone.user_create(
request,
name=request.DATA['name'],
email=request.DATA.get('email') or None,
password=request.DATA.get('password'),
project=request.DATA.get('project_id') or None,
enabled=True,
project=request.DATA.get('project') or None,
enabled=request.DATA.get('enabled', True),
description=request.DATA.get('description') or None,
domain=domain.id
)
@ -260,6 +262,17 @@ class Role(generic.View):
api.keystone.role_update(request, id, request.DATA['name'])
@urls.register
class DefaultDomain(generic.View):
"""API for default domain of the user."""
url_regex = r'keystone/default_domain/$'
@rest_utils.ajax()
def get(self, request):
"""Get a default domain of the user."""
return api.keystone.get_default_domain(request).to_dict()
@urls.register
class Domains(generic.View):
"""API over all domains."""

View File

@ -0,0 +1,66 @@
/**
* Copyright 2016 99Cloud
*
* 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';
/**
* @ngdoc overview
* @ngname horizon.dashboard.identity.users.actions
*
* @description
* Provides all of the actions for users.
*/
angular
.module('horizon.dashboard.identity.users.actions', [
'horizon.framework.conf'
])
.run(registerUserActions)
.config(config);
registerUserActions.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.identity.users.actions.create.service',
'horizon.dashboard.identity.users.resourceType'
];
function registerUserActions(
registry,
createService,
userResourceTypeCode
) {
var userResourceType = registry.getResourceType(userResourceTypeCode);
userResourceType.globalActions
.append({
id: 'createUserAction',
service: createService,
template: {
type: 'create',
text: gettext('Create User')
}
});
}
config.$inject = [
'$provide',
'$windowProvider'
];
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/users/actions/';
$provide.constant('horizon.dashboard.identity.users.actions.basePath', path);
}
})();

View File

@ -0,0 +1,86 @@
/**
* Copyright 2016 99Cloud
*
* 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';
angular
.module('horizon.dashboard.identity.users')
.factory('horizon.dashboard.identity.users.actions.create.service', createService);
createService.$inject = [
'$q',
'horizon.dashboard.identity.users.resourceType',
'horizon.dashboard.identity.users.actions.basePath',
'horizon.dashboard.identity.users.actions.workflow.service',
'horizon.app.core.openstack-service-api.keystone',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.widgets.form.ModalFormService',
'horizon.framework.widgets.toast.service'
];
/**
* @ngDoc factory
* @name horizon.dashboard.identity.users.actions.create.service
* @Description A service to open the user wizard.
*/
function createService(
$q,
resourceType,
basePath,
workflow,
keystone,
policy,
actionResultService,
modal,
toast
) {
var message = {
success: gettext('User %s was successfully created.')
};
return {
allowed: allowed,
perform: perform,
submit: submit
};
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:create_user']] });
}
function perform() {
var config = workflow.init("create");
config.title = gettext("Create User");
return modal.open(config).then(submit);
}
function submit(context) {
return keystone.createUser(context.model).then(success);
}
function success(response) {
var user = response.data;
toast.add('success', interpolate(message.success, [user.name]));
return actionResultService.getActionResult()
.created(resourceType, user.id)
.result;
}
}
})();

View File

@ -0,0 +1,91 @@
/**
* Copyright 2016 99Cloud
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use self 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('horizon.dashboard.identity.users.actions.create.service', function() {
var $q, $scope, keystone, service, modal, policy, resourceType, toast;
///////////////////////
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.dashboard.identity.users'));
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
$scope = _$rootScope_.$new();
$q = _$q_;
service = $injector.get('horizon.dashboard.identity.users.actions.create.service');
toast = $injector.get('horizon.framework.widgets.toast.service');
modal = $injector.get('horizon.framework.widgets.form.ModalFormService');
keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
resourceType = $injector.get('horizon.dashboard.identity.users.resourceType');
}));
it('should check the policy if the user is allowed to create user', function() {
var deferred = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(deferred.promise);
deferred.resolve({allowed: true});
var handler = jasmine.createSpyObj('handler', ['success']);
service.allowed().then(handler.success);
$scope.$apply();
expect(handler.success).toHaveBeenCalled();
var allowed = handler.success.calls.first().args[0];
expect(allowed).toBeTruthy();
expect(policy.ifAllowed).toHaveBeenCalledWith(
{ rules: [['identity', 'identity:create_user']] });
});
it('should open the modal', function() {
spyOn(modal, 'open').and.returnValue($q.defer().promise);
spyOn(keystone, 'getVersion').and.returnValue($q.defer().promise);
spyOn(keystone, 'getDefaultDomain').and.returnValue($q.defer().promise);
spyOn(keystone, 'getProjects').and.returnValue($q.defer().promise);
spyOn(keystone, 'getRoles').and.returnValue($q.defer().promise);
service.perform();
$scope.$apply();
expect(modal.open).toHaveBeenCalled();
});
it('should submit create user request to keystone', function() {
var deferred = $q.defer();
spyOn(keystone, 'createUser').and.returnValue(deferred.promise);
spyOn(toast, 'add').and.callFake(angular.noop);
var handler = jasmine.createSpyObj('handler', ['success']);
deferred.resolve({data: {name: 'saved', id: '12345'}});
service.submit({model: {name: 'entered'}}).then(handler.success);
$scope.$apply();
expect(keystone.createUser).toHaveBeenCalledWith({name: 'entered'});
expect(toast.add).toHaveBeenCalledWith('success', 'User saved was successfully created.');
expect(handler.success).toHaveBeenCalled();
var result = handler.success.calls.first().args[0];
expect(result.created).toEqual([{type: resourceType, id: '12345'}]);
});
});
})();

View File

@ -0,0 +1 @@
<p translate>Create a new user and set related properties including the Primary Project and Role.</p>

View File

@ -0,0 +1,176 @@
/**
* Copyright 2017 NEC Corporation
*
* 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';
/**
* @ngdoc factory
* @name horizon.dashboard.identity.users.actions.workflow.service
* @ngController
*
* @description
* Workflow for creating/updating user
*/
angular
.module('horizon.dashboard.identity.users.actions')
.factory('horizon.dashboard.identity.users.actions.workflow.service', UserWorkflow);
UserWorkflow.$inject = [
'horizon.app.core.openstack-service-api.keystone',
'horizon.dashboard.identity.users.basePath'
];
function UserWorkflow(keystone, basePath) {
var workflow = {
init: init
};
function init(action) {
var schema = {
type: 'object',
properties: {
domain_name: {
title: gettext('Domain Name'),
type: 'string',
readOnly: true
},
domain_id: {
title: gettext('Domain Id'),
type: 'string',
readOnly: true
},
name: {
title: gettext('User Name'),
type: 'string'
},
email: {
title: gettext('Email'),
type: 'string'
},
password: {
title: gettext('Password'),
type: 'string'
},
project: {
title: gettext('Primary Project'),
type: 'string'
},
role: {
title: gettext('Role'),
type: 'string'
},
description: {
title: gettext('Description'),
type: 'string'
},
enabled: {
title: gettext('Enabled'),
type: 'boolean',
default: true
}
},
required: ['name', 'password', 'project', 'role', 'enabled']
};
var form = [
{
type: 'section',
htmlClass: 'row',
items: [
{
type: 'section',
htmlClass: 'col-sm-12',
items: [
{
type: 'template',
templateUrl: basePath + "actions/workflow/info." + action + ".help.html"
},
{ key: 'domain_name' },
{ key: 'domain_id' },
{ key: 'name' },
{ key: 'email' },
{
key: 'password',
type: 'password'
},
{
key: 'confirm',
type: 'password-confirm',
title: 'Confirm Password',
match: 'model.password'
},
{
key: 'project',
type: 'select',
titleMap: []
},
{
key: 'role',
type: 'select',
titleMap: []
},
{ key: 'description' },
{ key: 'enabled' }
]
}
]
}
];
var model = {};
var config = {
schema: schema,
form: form,
model: model
};
keystone.getVersion().then(function (response) {
var apiVersion = response.data.version;
var domainName = config.form[0].items[0].items[1];
var domainId = config.form[0].items[0].items[2];
if (apiVersion !== "3") {
domainName.condition = true;
domainId.condition = true;
}
return apiVersion;
});
keystone.getDefaultDomain().then(function (response) {
config.model.domain_name = response.data.name;
config.model.domain_id = response.data.id;
return response.data;
});
keystone.getProjects().then(function (response) {
var projectField = config.form[0].items[0].items[7];
projectField.titleMap = response.data.items.map(function each(item) {
return {value: item.id, name: item.name};
});
});
keystone.getRoles().then(function (response) {
var roleField = config.form[0].items[0].items[8];
roleField.titleMap = response.data.items.map(function each(item) {
return {value: item.id, name: item.name};
});
});
return config;
}
return workflow;
}
})();

View File

@ -0,0 +1,71 @@
/**
* 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('horizon.dashboard.identity.users.actions.workflow.service', function() {
var $q, $scope, workflow, keystone;
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.dashboard.identity.users'));
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
$scope = _$rootScope_.$new();
$q = _$q_;
workflow = $injector.get('horizon.dashboard.identity.users.actions.workflow.service');
keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
}));
function testInitWorkflow(version) {
var deferredVersion = $q.defer();
var deferredDefaultDomain = $q.defer();
var deferredProjects = $q.defer();
var deferredRoles = $q.defer();
spyOn(keystone, 'getVersion').and.returnValue(deferredVersion.promise);
spyOn(keystone, 'getDefaultDomain').and.returnValue(deferredDefaultDomain.promise);
spyOn(keystone, 'getProjects').and.returnValue(deferredProjects.promise);
spyOn(keystone, 'getRoles').and.returnValue(deferredRoles.promise);
deferredVersion.resolve({data: {version: version}});
deferredDefaultDomain.resolve({data: {items: [{name: 'dom1', id: '12345'}]}});
deferredProjects.resolve({data: {items: [{name: 'proj1', id: '12345'}]}});
deferredRoles.resolve({data: {items: [{name: 'role1', id: '12345'}]}});
var config = workflow.init();
$scope.$apply();
expect(config.schema).toBeDefined();
expect(config.form).toBeDefined();
expect(config.model).toBeDefined();
return config;
}
it('should create workflow config for creation using Keystone V3', function() {
var config = testInitWorkflow('3');
expect(config.form[0].items[0].items[1].condition).not.toBeDefined();
expect(config.form[0].items[0].items[2].condition).not.toBeDefined();
});
it('should create workflow config and the config does not show domain info ' +
'when use Keystone V2', function() {
var config = testInitWorkflow('2');
expect(config.form[0].items[0].items[1].condition).toBe(true);
expect(config.form[0].items[0].items[2].condition).toBe(true);
});
});
})();

View File

@ -1,4 +1,5 @@
<hz-resource-panel resource-type-name="OS::Keystone::User">
<hz-resource-table resource-type-name="OS::Keystone::User">
<hz-resource-table resource-type-name="OS::Keystone::User"
track-by="trackBy">
</hz-resource-table>
</hz-resource-panel>

View File

@ -29,9 +29,11 @@
angular
.module('horizon.dashboard.identity.users', [
'ngRoute',
'horizon.dashboard.identity.users.details'
'horizon.dashboard.identity.users.details',
'horizon.dashboard.identity.users.actions'
])
.constant('horizon.dashboard.identity.users.resourceType', 'OS::Keystone::User')
.constant('horizon.dashboard.identity.users.validationRules', validationRules())
.run(run)
.config(config);
@ -120,6 +122,19 @@
}
}
/**
* @ngdoc constant
* @name horizon.dashboard.identity.users.events.validationRules
* @description constants for use in validation fields
*/
function validationRules() {
return {
integer: /^[0-9]+$/,
validatePassword: /.*/,
fieldMaxLength: 255
};
}
config.$inject = [
'$provide',
'$windowProvider',

View File

@ -19,7 +19,7 @@
describe('loading the module', function () {
var registry;
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.dashboard.identity.users'));
beforeEach(module('horizon.framework'));
beforeEach(inject(function($injector) {

View File

@ -57,7 +57,17 @@
* Returns a promise for the users data.
*/
function getUsersPromise(params) {
return keystone.getUsers(params);
return keystone.getUsers(params).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.name + item.email + item.project +
item.description + item.enabled;
return item;
}
}
}
/*

View File

@ -41,7 +41,9 @@
spyOn(keystone, 'getUsers').and.returnValue(deferred.promise);
var result = service.getUsersPromise();
deferred.resolve({data: {items: [{id: 1, name: 'puff'}]}});
deferred.resolve({data: {items: [{id: '1', name: 'puff'}]}});
scope.$apply();
expect(keystone.getUsers).toHaveBeenCalled();
expect(result.$$state.value.data.items[0].name).toBe('puff');
@ -58,7 +60,7 @@
service.getUserPromise(1);
deferredVersion.resolve({data: {version: ''}});
deferredUser.resolve({data: {id: 1, name: 'puff'}});
deferredUser.resolve({data: {id: '1', name: 'puff'}});
scope.$apply();
expect(keystone.getVersion).toHaveBeenCalled();
@ -73,7 +75,7 @@
service.getUserPromise(1);
deferredVersion.resolve({data: {version: 2}});
deferredUser.resolve({data: {id: 1, name: 'puff'}});
deferredUser.resolve({data: {id: '1', name: 'puff'}});
scope.$apply();
expect(keystone.getVersion).toHaveBeenCalled();
@ -92,10 +94,10 @@
var result = service.getUserPromise(1);
deferredVersion.resolve({data: {version: 3}});
deferredUser.resolve({data: {id: 1, name: 'puff', domain_id: 29,
deferredUser.resolve({data: {id: '1', name: 'puff', domain_id: 29,
default_project_id: 26}});
deferredProject.resolve({data: {name: 'puff_project'}});
deferredDomain.resolve({data: {id: 1, name: 'puff_domain'}});
deferredDomain.resolve({data: {id: '1', name: 'puff_domain'}});
scope.$apply();
expect(keystone.getVersion).toHaveBeenCalled();

View File

@ -42,6 +42,7 @@
getRole: getRole,
editRole: editRole,
deleteRole: deleteRole,
getDefaultDomain: getDefaultDomain,
getDomains: getDomains,
createDomain: createDomain,
deleteDomains: deleteDomains,
@ -256,6 +257,13 @@
}
// Domains
function getDefaultDomain() {
return apiService.get('/api/keystone/default_domain/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve the default domain.'));
});
}
function getDomains() {
return apiService.get('/api/keystone/domains/')
.error(function () {

View File

@ -209,6 +209,12 @@
42
]
},
{
"func": "getDefaultDomain",
"method": "get",
"path": "/api/keystone/default_domain/",
"error": "Unable to retrieve the default domain."
},
{
"func": "getDomains",
"method": "get",

View File

@ -95,37 +95,40 @@ class KeystoneRestTestCase(test.TestCase):
def test_user_create_full(self):
self._test_user_create(
'{"name": "bob", '
'"password": "sekrit", "project_id": "project123", '
'"email": "spam@company.example"}',
'"password": "sekrit", "project": "123", '
'"email": "spam@company.example", '
'"description": "hello, puff"}',
{
'name': 'bob',
'password': 'sekrit',
'email': 'spam@company.example',
'project': 'project123',
'project': '123',
'domain': 'the_domain',
'enabled': True
'enabled': True,
'description': 'hello, puff'
}
)
def test_user_create_existing_role(self):
self._test_user_create(
'{"name": "bob", '
'"password": "sekrit", "project_id": "project123", '
'"password": "sekrit", "project": "123", '
'"email": "spam@company.example"}',
{
'name': 'bob',
'password': 'sekrit',
'email': 'spam@company.example',
'project': 'project123',
'project': '123',
'domain': 'the_domain',
'enabled': True
'enabled': True,
'description': None
}
)
def test_user_create_no_project(self):
self._test_user_create(
'{"name": "bob", '
'"password": "sekrit", "project_id": "", '
'"password": "sekrit", "project": "", '
'"email": "spam@company.example"}',
{
'name': 'bob',
@ -133,20 +136,22 @@ class KeystoneRestTestCase(test.TestCase):
'email': 'spam@company.example',
'project': None,
'domain': 'the_domain',
'enabled': True
'enabled': True,
'description': None
}
)
def test_user_create_partial(self):
self._test_user_create(
'{"name": "bob"}',
'{"name": "bob", "project": ""}',
{
'name': 'bob',
'password': None,
'email': None,
'project': None,
'domain': 'the_domain',
'enabled': True
'enabled': True,
'description': None
}
)
@ -365,6 +370,13 @@ class KeystoneRestTestCase(test.TestCase):
#
# Domains
#
@mock.patch.object(keystone.api, 'keystone')
def test_default_domain_get(self, kc):
request = self.mock_rest_request()
response = keystone.DefaultDomain().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.json, {})
@mock.patch.object(keystone.api, 'keystone')
def test_domain_get(self, kc):
request = self.mock_rest_request()