seed-jsbox-utils
Version:
JSbox specific functions
537 lines (460 loc) • 17.7 kB
JavaScript
/*jshint -W083 */
var assert = require('assert');
var libphonenumber = require('libphonenumber-js/max');
var moment = require('moment');
var vumigo = require('vumigo_v02');
var Choice = vumigo.states.Choice;
var _ = require('lodash');
var url_utils = require('url');
// JS-BOX SPECIFIC UTILS
// FIXTURES HELPERS
/**: check_fixtures_used(api, expected_used)
Checks expected fixtures used during testing against actual
:param object api: Vumigo sandbox API object
:param Array expected_used: an array of numbers representing the expected fixtures used
*/
check_fixtures_used = function(api, expected_used) {
var fixts = api.http.fixtures.fixtures;
var fixts_used = [];
fixts.forEach(function(f, i) {
f.uses > 0 ? fixts_used.push(i) : null;
});
assert.deepEqual(fixts_used, expected_used);
};
// TIMEOUT HELPERS
/**: timed_out(im)
Used to determine whether a time-out has occurred and redirection need to happen.
The current state should not be listed in the no_timeout_redirects property within
the config environment.
:param object im: Vumigo Interaction Machine object
*/
timed_out = function(im) {
return im.msg.session_event === 'new'
&& im.user.state.name
&& im.config.no_timeout_redirects.indexOf(im.user.state.name) === -1;
};
/**: timeout_redirect(im)
Tests whether a state is listed in the timeout_redirects property within the
config environment.
:param object im: Vumigo Interaction Machine object
*/
timeout_redirect = function(im) {
return im.config.timeout_redirects.indexOf(im.user.state.name) !== -1;
};
// REQUEST HELPERS
/**: get_paginated_response(service, url, params)
Gets all pages of a paginated request.
:param object service: The api for the service being queried.
:param string url: The url to send the request to.
:param object params: The query parameters for the request
*/
get_paginated_response = function(service, endpoint, params) {
url = service.base_url + endpoint;
// Get the current page and log the call
return service.http_api.get(url, {params: params})
.then(service.log_service_call('GET', url, null, params))
.then(function(response) {
var results = response.data.results;
if (response.data.next === null) {
return {results: results, count: results.length};
}
var next_url = url_utils.parse(response.data.next, true);
// Recursively get next pages
return get_paginated_response(service, endpoint, next_url.query).
then(function(page) {
// Tack subsequent pages onto current page
var combined_results = results.concat(page.results);
return {results: combined_results,
count:combined_results.length};
});
});
};
// NUMBER HELPERS
/**: check_valid_number(number)
Checks the validity of a number (An attempt to solve the insanity of JavaScript numbers)
:param string number: number to check
*/
check_valid_number = function(number) {
var numbers_only = new RegExp('^\\d+$');
return number !== ''
&& numbers_only.test(number)
&& !Number.isNaN(Number(number));
};
/**: double_digit_number(input)
If a number is single-digit, convert to double, else return as is.
:param string input: number
*/
double_digit_number = function(input) {
input_numeric = parseInt(input, 10);
if (parseInt(input, 10) < 10) {
return "0" + input_numeric.toString();
} else {
return input_numeric.toString();
}
};
/**: check_number_in_range(input, start, end)
Checks whether a number is within a certain range.
:param string input: number to check
:param string start: lower bound of range
:param string end: upper bound of range
*/
check_number_in_range = function(input, start, end) {
return this.check_valid_number(input) &&
(parseInt(input, 10) >= start) &&
(parseInt(input, 10) <= end);
};
/**: readable_msisdn(msisdn, country_code)
Returns msisdn as 'readable' (leading country code effectively replaced with '0')
:param string msisdn: 'normalized' msisdn to process
:param string country_code: specific country_code within given msisdn to replace
*/
readable_msisdn = function(msisdn, country_code) {
if (country_code[0] !== '+') {
country_code = '+' + country_code;
}
switch (['+','0'].indexOf(msisdn[0])) {
case 0: // msisdn starts with '+'
readable_no = msisdn.replace(country_code, '0');
break;
case 1: // msisdn starts with '0'
return msisdn; // assuming msisdn already readable
default: // msisdn starts with some other digit
msisdn = '+' + msisdn;
readable_no = msisdn.replace(country_code, '0');
}
return readable_no;
},
// MSISDN HELPERS
/**: is_valid_msisdn(number, country_code)
Check that number is a phone number
:param string number: number to check
:param string country_code: the country code, eg. ZA
*/
is_valid_msisdn = function (number, country_code) {
var phoneNumber = libphonenumber.parsePhoneNumberFromString(number, country_code);
return !!phoneNumber && phoneNumber.isValid();
};
/**: normalize_msisdn(raw, country_code)
Normalizes phone number based on specific country code.
Shortcodes are an exception and get preserved.
:param string raw: phone number
:param string country_code: country code for the number, eg. ZA
*/
normalize_msisdn = function(raw, country_code) {
// don't touch shortcodes
if (raw.length <= 5) {
return raw;
}
// Add + if the number starts with the country code
var country_number = libphonenumber.getCountryCallingCode(country_code);
if(raw.substr(0, country_number.length) === country_number){
raw = "+" + raw;
}
// Return formatted number
return libphonenumber.parsePhoneNumberFromString(raw, country_code).number;
};
// DATE HELPERS
/**: get_timestamp(format)
Returns current timestamp.
:param string format: Optional. If specified, will return timestamp in preferred format.
*/
get_timestamp = function(format) {
if (format) {
return moment().format(format);
} else {
return moment().format("YYYYMMDDHHmmss");
}
};
/**: get_moment_date(date, format)
Turns a passed in string date and that date's format into a moment object
If no date and format is passed in, it will return the current date as a moment object
:param string date: Optional.
:param string format: Optional. Specifies the format of the date passed in
e.g. 'YYYY-MM-DD hh:mm:ss', "YYMMDD", etc. Defaults to 'YYYY-MM-DD'
*/
get_moment_date = function(date, format) {
if (date) {
return new moment(date, format || 'YYYY-MM-DD');
} else {
return new moment();
}
};
/**: get_january(date)
Returns the current year's january 1st moment date
:param string date: date
*/
get_january = function(date) {
// returns current year january 1st moment date
return this.get_moment_date(date).startOf('year');
};
/**: is_valid_date(date, format)
Checks date validity.
:param string date: date to check
:param string format: the format of the date (e.g. 'YYYY-MM-DD')
*/
is_valid_date = function(date, format) {
// implements strict validation with 'true' below
return moment(date, format, true).isValid();
};
/**: is_valid_edd(date, maximum_weeks)
Checks EDD validity.
:param string date: date to check
:param string format: the format of the date (e.g. 'YYYY-MM-DD')
:param object config: configuration object
:param string maximum_weeks: how far back the date can be
*/
is_valid_edd = function(date, format, config, maximum_weeks) {
// implements strict validation with 'true' below
today = get_moment_date(config.testing_today, format);
edd_date = moment(date, format);
weeks = parseInt(moment.duration(edd_date - today).asWeeks());
return edd_date > today && weeks < maximum_weeks;
};
/**: is_valid_year(year, minYear, maxYear)
Checks that the number representing year is within the range determined by the
minYear & maxYear parameters
:param string year: year to be checked
:param string minYear: lower bound of check
:param string maxYear: upper bound of check
*/
is_valid_year = function(year, minYear, maxYear) {
return this.check_valid_number(year)
&& parseInt(year, 10) >= parseInt(minYear, 10)
&& parseInt(year, 10) <= parseInt(maxYear, 10);
};
/**: is_valid_day_of_month(day)
Checks that the number representing the day of the month is within a valid range
:param string day: day to be validated
:param string month: Optional. Month to be validated.
:param string year: Optional. Year to be validated.
*/
is_valid_day_of_month = function(day, month, year) {
if (month && year) {
return this.is_valid_date(year + "-" + month + "-" + day, "YYYY-M-D");
} else if (month) {
return this.is_valid_date("2000-" + month + "-" + day, "YYYY-M-D");
} else {
// check that it is a number and between 1 and 31
return this.check_valid_number(day)
&& parseInt(day, 10) >= 1
&& parseInt(day, 10) <= 31;
}
};
/**: get_entered_birth_date(year, month, day, separator_char)
Concatenates year, month and day strings.
:param string year: year
:param string month: month
:param string day: day
:param string separator_char: separator character
*/
// simply concatenates year, month and day (optional: separator specified)
get_entered_birth_date = function(year, month, day, separator_char) {
var sc = separator_char || '-';
return year + sc + month + sc + this.double_digit_number(day);
};
// TEXT HELPERS
/**: check_valid_alpha(input)
Checks whether all characters in input string are in standard alphabet
:param string input: string to check
*/
check_valid_alpha = function(input) {
//
var alpha_only = new RegExp('^[A-Za-z]+$');
return input !== '' && alpha_only.test(input);
};
/**: is_alpha_numeric_only(input)
Checks whether input string consist of alpha-numberics
:param object input: string to check
*/
is_alpha_numeric_only = function(input) {
alpha_numeric = new RegExp('^[A-Za-z0-9]+$');
return alpha_numeric.test(input);
};
/**: is_valid_name(input, min, max)
Check that the input string does not include characters normally not found in
names. Also includes a length check; input string length needs to be within
min-max range
:param string input: string to check
:param string min: minimum length
:param string max: maximum length
*/
is_valid_name = function(input, min, max) {
var name_check = new RegExp(
'(^[^±!@£$%^&*_+§¡€#¢§¶•ªº«\\/<>?:;|=.,0123456789]{min,max}$)'
.replace('min', min.toString())
.replace('max', max.toString())
);
return input !== '' && name_check.test(input);
};
/**: get_clean_first_word(user_message)
Extracts first word from a message.
:param string user_message: message to extract first word from
*/
get_clean_first_word = function(user_message) {
return user_message
.trim() // remove whitespace from start and end
.split(" ")[0] // split off first word
.replace(/\W/g, '') // remove non letters
.toUpperCase(); // capitalise
};
// ID HELPERS
/**: extract_za_id_dob(id)
Extracts the date of birth from a South African identification number
:param string id: id number
*/
extract_za_id_dob = function(id) {
var id_dob = moment(id.slice(0,6), 'YYMMDD').format('YYYY-MM-DD');
var dob_century = id_dob.slice(0,2);
var dob_two_digit_year = id_dob.slice(2,4);
// override moment default century switch at '68 with '49
if (dob_two_digit_year > 49) {
id_dob = id_dob.replace(dob_century, '19');
}
return id_dob;
};
/**: validate_id_za(id)
Validates South African identification number
:param string id: id number
*/
validate_id_za = function(id) {
var i, c,
even = '',
sum = 0,
check = id.slice(-1);
if (id.length != 13 || id.match(/\D/)) {
return false;
}
if (!moment(id.slice(0,6), 'YYMMDD', true).isValid()) {
return false;
}
id = id.substr(0, id.length - 1);
for (i = 0; id.charAt(i); i += 2) {
c = id.charAt(i);
sum += +c;
even += id.charAt(i + 1);
}
even = '' + even * 2;
for (i = 0; even.charAt(i); i++) {
c = even.charAt(i);
sum += +c;
}
sum = 10 - ('' + sum).charAt(1);
return ('' + sum).slice(-1) == check;
};
// BOOLEAN HELPERS
/**: is_true(bool)
Checks whether parameter given is not undefined and true
:param string/boolean bool: parameter to check
*/
is_true = function(bool) {
//If is is not undefined and boolean is true
return (!_.isUndefined(bool) && (bool==='true' || bool===true));
};
// CHOICE HELPERS
/**: make_month_choices($, startDate, limit, increment, valueFormat, labelFormat)
Works in conjunction with vumigo Choice-/PaginatedChoiceState providing month choices
according to number required in listing. Listing can go forward/backward in increments,
and be displayed in the necessary value/label format.
Returns an array of Choice objects.
:param object $:
:param string startDate: the starting date of the Choice listing to be build.
:param string limit: the number of Choice objects required in listing
:param string increment: positive/negative incremental steps of month/Choice objects
:param string valueFormat:
required value format (see valid format options on http://momentjs.com/docs/#/displaying/)
- valueFormat affects value property of Choice object (choice.value)
:param string labelFormat:
required label format (see valid format options on http://momentjs.com/docs/#/displaying/)
- labelFormat affects label property of Choice object (choice.label)
*/
make_month_choices = function($, startDate, limit, increment, valueFormat, labelFormat) {
// Currently supports month translation in formats MMMM and MM
var choices = [];
var monthIterator = startDate;
for (var i=0; i<limit; i++) {
var raw_label = monthIterator.format(labelFormat);
var prefix, suffix, month, translation;
var quad_month_index = labelFormat.indexOf("MMMM");
var trip_month_index = labelFormat.indexOf("MMM");
if (quad_month_index > -1) {
month = monthIterator.format("MMMM");
prefix = raw_label.substring(0, quad_month_index);
suffix = raw_label.substring(quad_month_index+month.length, raw_label.length);
translation = {
January: $("{{pre}}January{{post}}"),
February: $("{{pre}}February{{post}}"),
March: $("{{pre}}March{{post}}"),
April: $("{{pre}}April{{post}}"),
May: $("{{pre}}May{{post}}"),
June: $("{{pre}}June{{post}}"),
July: $("{{pre}}July{{post}}"),
August: $("{{pre}}August{{post}}"),
September: $("{{pre}}September{{post}}"),
October: $("{{pre}}October{{post}}"),
November: $("{{pre}}November{{post}}"),
December: $("{{pre}}December{{post}}"),
};
translated_label = translation[month].context({
pre: prefix,
post: suffix
});
} else if (trip_month_index > -1) {
month = monthIterator.format("MMM");
prefix = raw_label.substring(0, trip_month_index);
suffix = raw_label.substring(trip_month_index+month.length, raw_label.length);
translation = {
Jan: $("{{pre}}Jan{{post}}"),
Feb: $("{{pre}}Feb{{post}}"),
Mar: $("{{pre}}Mar{{post}}"),
Apr: $("{{pre}}Apr{{post}}"),
May: $("{{pre}}May{{post}}"),
Jun: $("{{pre}}Jun{{post}}"),
Jul: $("{{pre}}Jul{{post}}"),
Aug: $("{{pre}}Aug{{post}}"),
Sep: $("{{pre}}Sep{{post}}"),
Oct: $("{{pre}}Oct{{post}}"),
Nov: $("{{pre}}Nov{{post}}"),
Dec: $("{{pre}}Dec{{post}}"),
};
translated_label = translation[month].context({
pre: prefix,
post: suffix
});
} else {
// assume numbers don't need translation
translated_label = raw_label;
}
choices.push(new Choice(monthIterator.format(valueFormat),
translated_label));
monthIterator.add(increment, 'months');
}
return choices;
};
module.exports = {
check_fixtures_used: check_fixtures_used,
timed_out: timed_out,
timeout_redirect: timeout_redirect,
check_valid_number: check_valid_number,
double_digit_number: double_digit_number,
check_number_in_range: check_number_in_range,
readable_msisdn: readable_msisdn,
is_valid_msisdn: is_valid_msisdn,
normalize_msisdn: normalize_msisdn,
get_january: get_january,
get_timestamp: get_timestamp,
get_moment_date: get_moment_date,
is_valid_date: is_valid_date,
is_valid_edd: is_valid_edd,
is_valid_year: is_valid_year,
is_valid_day_of_month: is_valid_day_of_month,
get_entered_birth_date: get_entered_birth_date,
check_valid_alpha: check_valid_alpha,
is_alpha_numeric_only: is_alpha_numeric_only,
is_valid_name: is_valid_name,
get_clean_first_word: get_clean_first_word,
extract_za_id_dob: extract_za_id_dob,
validate_id_za: validate_id_za,
is_true: is_true,
make_month_choices: make_month_choices,
get_paginated_response: get_paginated_response
};