Refactor server dialogue validation for better unit testing.

Victoria & Joao @ Pivotal.
pull/9/head
Victoria Henry 2018-03-13 14:47:32 -04:00 committed by Dave Page
parent 156b308fd3
commit 6b03cb78af
10 changed files with 299 additions and 111 deletions

View File

@ -18,7 +18,7 @@
"file-loader": "^0.11.2",
"image-webpack-loader": "^3.3.1",
"is-docker": "^1.1.0",
"jasmine-core": "~3.1.0",
"jasmine-core": "~2.99.0",
"jasmine-enzyme": "~4.1.1",
"karma": "~1.5.0",
"karma-babel-preprocessor": "^6.0.1",
@ -69,6 +69,7 @@
"hard-source-webpack-plugin": "^0.4.9",
"immutability-helper": "^2.2.0",
"imports-loader": "^0.7.1",
"ip-address": "^5.8.9",
"jquery": "1.11.2",
"jquery-contextmenu": "^2.5.0",
"jquery-ui": "^1.12.1",

View File

@ -2,10 +2,13 @@ define('pgadmin.node.server', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'underscore.string', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.server.supported_servers', 'pgadmin.user_management.current_user',
'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.browser.server.privilege',
'pgadmin.alertifyjs', 'pgadmin.backform',
'sources/browser/server_groups/servers/model_validation',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, $, _, Backbone, S, pgAdmin, pgBrowser,
supported_servers, current_user, Alertify, Backform
supported_servers, current_user, Alertify, Backform,
modelValidation
) {
if (!pgBrowser.Nodes['server']) {
@ -848,110 +851,8 @@ define('pgadmin.node.server', [
group: gettext('Connection'),
}],
validate: function() {
var err = {},
errmsg,
self = this;
var service_id = this.get('service');
var check_for_empty = function(id, msg) {
var v = self.get(id);
if (
_.isUndefined(v) || v === null || String(v).replace(/^\s+|\s+$/g, '') == ''
) {
err[id] = msg;
errmsg = errmsg || msg;
return true;
} else {
self.errorModel.unset(id);
return false;
}
};
var check_for_valid_ipv6 = function(val){
// Regular expression for validating IPv6 address formats
var exps = ['^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|',
'(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|',
'2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|',
'(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|',
':((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|',
'(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|',
'2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|',
'(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|',
'[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|',
'((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|',
'(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|',
'1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|',
'((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$'];
var exp = new RegExp(exps.join(''));
return exp.test(val.trim());
};
var check_for_valid_ip = function(id, msg) {
var v4exps = '(^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)';
var v4exp = new RegExp(v4exps);
var v = self.get(id);
if (
v && !(v4exp.test(v.trim()))
) {
if(!check_for_valid_ipv6(v)){
err[id] = msg;
errmsg = msg;
}
} else {
self.errorModel.unset(id);
}
};
if (!self.isNew() && 'id' in self.sessAttrs) {
err['id'] = gettext('The ID cannot be changed.');
errmsg = err['id'];
} else {
self.errorModel.unset('id');
}
check_for_empty('name', gettext('Name must be specified.'));
// If no service id then only check
if (
_.isUndefined(service_id) || _.isNull(service_id) ||
String(service_id).replace(/^\s+|\s+$/g, '') == ''
) {
if (check_for_empty(
'host', gettext('Either Host name, Address or Service must be specified.')
) && check_for_empty('hostaddr', gettext('Either Host name, Address or Service must be specified.'))){
errmsg = errmsg || gettext('Either Host name, Address or Service must be specified.');
} else {
errmsg = undefined;
delete err['host'];
delete err['hostaddr'];
}
check_for_empty(
'db', gettext('Maintenance database must be specified.')
);
check_for_valid_ip(
'hostaddr', gettext('Host address must be valid IPv4 or IPv6 address.')
);
check_for_valid_ip(
'hostaddr', gettext('Host address must be valid IPv4 or IPv6 address.')
);
} else {
_.each(['host', 'hostaddr', 'db'], (item) => {
self.errorModel.unset(item);
});
}
check_for_empty(
'username', gettext('Username must be specified.')
);
check_for_empty('port', gettext('Port must be specified.'));
this.errorModel.set(err);
if (_.size(err)) {
return errmsg;
}
return null;
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
},
isConnected: function(model) {
return model.get('connected');

View File

@ -1,6 +1,6 @@
define('bundled_browser',[
'pgadmin.browser',
'sources/browser/server_groups/servers/databases/external_tables/index',
'sources/browser/index',
], function(pgBrowser) {
pgBrowser.init();
});

View File

@ -0,0 +1,10 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import 'server_groups';

View File

@ -0,0 +1,10 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import 'servers';

View File

@ -0,0 +1,10 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import 'external_tables';

View File

@ -0,0 +1,11 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import 'databases';
import 'model_validation';

View File

@ -0,0 +1,104 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import _ from 'underscore';
import {Address4, Address6} from 'ip-address';
export class ModelValidation {
constructor(model) {
this.err = {};
this.errmsg = '';
this.model = model;
}
validate() {
const serviceId = this.model.get('service');
if (!this.model.isNew() && 'id' in this.model.sessAttrs) {
this.err['id'] = gettext('The ID cannot be changed.');
this.errmsg = this.err['id'];
} else {
this.model.errorModel.unset('id');
}
this.checkForEmpty('name', gettext('Name must be specified.'));
if (ModelValidation.isEmptyString(serviceId)) {
this.checkHostAndHostAddress();
this.checkForEmpty('db', gettext('Maintenance database must be specified.'));
} else {
this.clearHostAddressAndDbErrors();
}
this.checkForEmpty('username', gettext('Username must be specified.'));
this.checkForEmpty('port', gettext('Port must be specified.'));
this.model.errorModel.set(this.err);
if (_.size(this.err)) {
return this.errmsg;
}
return null;
}
clearHostAddressAndDbErrors() {
_.each(['host', 'hostaddr', 'db'], (item) => {
this.model.errorModel.unset(item);
});
}
checkHostAndHostAddress() {
const translatedStr = gettext('Either Host name, Address or Service must ' +
'be specified.');
if (this.checkForEmpty('host', translatedStr) &&
this.checkForEmpty('hostaddr', translatedStr)) {
this.errmsg = this.errmsg || translatedStr;
} else {
this.errmsg = undefined;
delete this.err['host'];
delete this.err['hostaddr'];
}
this.checkForValidIp(this.model.get('hostaddr'),
gettext('Host address must be valid IPv4 or IPv6 address.'));
}
checkForValidIp(ipAddress, msg) {
if (ipAddress) {
const isIpv6Address = new Address6(ipAddress).isValid();
const isIpv4Address = new Address4(ipAddress).isValid();
if (!isIpv4Address && !isIpv6Address) {
this.err['hostaddr'] = msg;
this.errmsg = msg;
}
} else {
this.model.errorModel.unset('hostaddr');
}
}
checkForEmpty(id, msg) {
const value = this.model.get(id);
if (ModelValidation.isEmptyString(value)) {
this.err[id] = msg;
this.errmsg = this.errmsg || msg;
return true;
} else {
this.model.errorModel.unset(id);
return false;
}
}
static isEmptyString(string) {
return _.isUndefined(string) || _.isNull(string) || string.trim() === '';
}
}

View File

@ -0,0 +1,101 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import {ModelValidation} from 'sources/browser/server_groups/servers/model_validation';
describe('Server#ModelValidation', () => {
describe('When validating a server parameters', () => {
let model;
let modelValidation;
beforeEach(() => {
model = {
errorModel: jasmine.createSpyObj('errorModel', ['set', 'unset']),
allValues: {},
get: function (key) {
return this.allValues[key];
},
sessAttrs: {},
};
model.isNew = jasmine.createSpy('isNew');
modelValidation = new ModelValidation(model);
});
describe('When all parameters are valid', () => {
beforeEach(() => {
model.isNew.and.returnValue(true);
model.allValues['name'] = 'some name';
model.allValues['username'] = 'some username';
model.allValues['port'] = 'some port';
});
describe('No service id', () => {
it('does not set any error in the model', () => {
model.allValues['host'] = 'some host';
model.allValues['db'] = 'some db';
model.allValues['hostaddr'] = '1.1.1.1';
expect(modelValidation.validate()).toBeNull();
expect(model.errorModel.set).toHaveBeenCalledWith({});
});
});
describe('Service id present', () => {
it('does not set any error in the model', () => {
model.allValues['service'] = 'asdfg';
expect(modelValidation.validate()).toBeNull();
expect(model.errorModel.set).toHaveBeenCalledWith({});
});
});
});
describe('When no parameters are valid', () => {
describe('Service id not present', () => {
it('does not set any error in the model', () => {
expect(modelValidation.validate()).toBe('Name must be specified.');
expect(model.errorModel.set).toHaveBeenCalledTimes(1);
expect(model.errorModel.set).toHaveBeenCalledWith({
name: 'Name must be specified.',
host: 'Either Host name, Address or Service must be specified.',
hostaddr: 'Either Host name, Address or Service must be specified.',
db: 'Maintenance database must be specified.',
username: 'Username must be specified.',
port: 'Port must be specified.'
});
});
});
describe('Host address is not valid', () => {
it('sets the "Host address must be a valid IPv4 or IPv6 address" error', () => {
model.allValues['hostaddr'] = 'something that is not an ip address';
expect(modelValidation.validate()).toBe('Host address must be valid IPv4 or IPv6 address.');
expect(model.errorModel.set).toHaveBeenCalledTimes(1);
expect(model.errorModel.set).toHaveBeenCalledWith({
name: 'Name must be specified.',
hostaddr: 'Host address must be valid IPv4 or IPv6 address.',
db: 'Maintenance database must be specified.',
username: 'Username must be specified.',
port: 'Port must be specified.'
});
});
});
describe('Service id present', () => {
it('does not set any error in the model', () => {
model.allValues['service'] = 'asdfg';
expect(modelValidation.validate()).toBe('Name must be specified.');
expect(model.errorModel.set).toHaveBeenCalledTimes(1);
expect(model.errorModel.set).toHaveBeenCalledWith({
name: 'Name must be specified.',
username: 'Username must be specified.',
port: 'Port must be specified.'
});
});
});
});
});
});

View File

@ -3938,6 +3938,18 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ip-address@^5.8.9:
version "5.8.9"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.8.9.tgz#6379277c23fc5adb20511e4d23ec2c1bde105dfd"
dependencies:
jsbn "1.1.0"
lodash.find "^4.6.0"
lodash.max "^4.0.1"
lodash.merge "^4.6.0"
lodash.padstart "^4.6.1"
lodash.repeat "^4.1.0"
sprintf-js "1.1.0"
ip-regex@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
@ -4267,9 +4279,9 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
jasmine-core@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.1.0.tgz#a4785e135d5df65024dfc9224953df585bd2766c"
jasmine-core@~2.99.0:
version "2.99.1"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.99.1.tgz#e6400df1e6b56e130b61c4bcd093daa7f6e8ca15"
jasmine-enzyme@~4.1.1:
version "4.1.1"
@ -4329,6 +4341,10 @@ js-yaml@~3.7.0:
argparse "^1.0.7"
esprima "^2.6.0"
jsbn@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -4738,6 +4754,10 @@ lodash.escape@^3.0.0:
dependencies:
lodash._root "^3.0.0"
lodash.find@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@ -4777,6 +4797,10 @@ lodash.keys@^3.0.0:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.max@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.max/-/lodash.max-4.0.1.tgz#8735566c618b35a9f760520b487ae79658af136a"
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -4785,10 +4809,22 @@ lodash.memoize@~3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
lodash.merge@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
lodash.mergewith@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
lodash.padstart@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
lodash.repeat@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lodash.repeat/-/lodash.repeat-4.1.0.tgz#fc7de8131d8c8ac07e4b49f74ffe829d1f2bec44"
lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
@ -6863,6 +6899,10 @@ spectrum-colorpicker@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/spectrum-colorpicker/-/spectrum-colorpicker-1.8.0.tgz#b926cf5002c0a77860b5f8351e1c093c65200107"
sprintf-js@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.0.tgz#cffcaf702daf65ea39bb4e0fa2b299cec1a1be46"
sprintf-js@^1.0.3:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c"