From 5c79fc75f2dbe57f1de34736a00397b490dd4746 Mon Sep 17 00:00:00 2001 From: Sergii Leonenko Date: Tue, 26 Jan 2021 21:02:03 +0200 Subject: [PATCH] TT#108902 As a Developer, I want to transform between human readable time periods and Perl library Time::Period based time periods back and forth AC: Can transform human readable time periods [Weekday, StartHour, StartMinute, EndHour, EndMinute] to the Perl based ( function toKamailioPeriods(arrOfHumanReadablePeriods)) Can transform Perl based time periods to human readable ones [Weekday, StartHour, StartMinute, EndHour, EndMinute] Can see the two functions in a new separate file Change-Id: I6559fcf1639487b00e9e442a7d6e97907f1c9af5 --- src/helpers/kamailio-timesets-converter.js | 337 ++++++++++++++++ t/unit/kamailio-timesets-converter.js | 373 +++++++++++++++++ .../kamailio-timesets-converter.spec.js | 377 ++++++++++++++++++ 3 files changed, 1087 insertions(+) create mode 100644 src/helpers/kamailio-timesets-converter.js create mode 100644 t/unit/kamailio-timesets-converter.js create mode 100644 test/jest/__tests__/helpers/kamailio-timesets-converter.spec.js diff --git a/src/helpers/kamailio-timesets-converter.js b/src/helpers/kamailio-timesets-converter.js new file mode 100644 index 00000000..e37758b7 --- /dev/null +++ b/src/helpers/kamailio-timesets-converter.js @@ -0,0 +1,337 @@ +// /* eslint-disable */ + +/* +humanReadableTimeset = [{ + weekday: 1..7, + from: '0:00', + to: '23:59' + + //Seconds are NOT SUPPORTED !!! + //Time "23:59" in "to" field is considering as "24:00" !!! +}, ... +] + +kamailioTimeset = [{ + //SUPPORTED + wday - Day of the week, either a number between 1 and 7, or at least the first 2 letters of a spelled out weekday name (analogous to the “month” scale). Sunday is the first day of the week. + hour - A number between 0 and 23. Unlike the Perl Time::Period module, “am” or “pm” specifications are not supported. + minute - A number between 0 and 59. + + //NOT SUPPORTED !!! + reversed ranges, like: "hour {15-3}" + complex ranges, like: "hour {1 2 5-8}" + Aliases: wd, hr, min + + year or yr - Either given as a full 4-digit number >= 1970, or as a 2-digit number, in which case it will be understood to be within the current century. + month or mo - Month of the year, either a number between 1 and 12, or at least the first 3 letters of a spelled out month name, e.g. “jan”, “janua” or “january” will all work. + week or wk - Week of the month, a number between 1 and 6. The first day of the week is Sunday. + yday or yd - Day of the year, a number between 1 and 366. + mday or md - Day of the month, a number between 1 and 31. + second or sec - A number between 0 and 60 (to allow for leap seconds). +}, ... +] +*/ + +export function getTimeStrElements(timeStr) { + const [hours, minutes] = timeStr.split(':').map(t => parseInt(t, 10)) + return { hours, minutes } +} + +/** + * @param timeStr - a string with hour and minutes divided by colon, like "0:00" or "23:59" + * @returns {number} + */ +export function timeStrToMinutes(timeStr) { + const { hours, minutes } = getTimeStrElements(timeStr) + return hours * 60 + minutes +} + +export function isTimeStrValid(timeStr) { + if (typeof timeStr === 'string') { + const { hours, minutes } = getTimeStrElements(timeStr) + return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 + } + return false +} + +export function validateHumanTimesets(hTimeset) { + hTimeset.forEach(timesetItem => { + const { weekday, from, to } = timesetItem + if (typeof weekday !== 'number' || isNaN(weekday) || weekday < 1 || weekday > 7 || + !isTimeStrValid(from) || !isTimeStrValid(to) + ) { + throw Error('A human timeset item has invalid format: ' + JSON.stringify(timesetItem)) + } + else if (timeStrToMinutes(from) > timeStrToMinutes(to)) { + throw Error('A human timeset item should have "from" time < or = "to" time: ' + JSON.stringify(timesetItem)) + } + }) +} + +/** + * Function resort timesets by "weekday" and "from" and combines closest timesets or duplicates + * Important! Before use this function please validate you timeses for correctness. + * @param hTimeset {humanReadableTimeset} + * @returns {humanReadableTimeset} + */ +export function getHumanTimesetsNormalized(hTimeset = []) { + //sort timeset by "weekday" and "from" columns + //clone input data to prevent original data object mutation + const hTimesetCloned = hTimeset.map(i => ({...i})) + const htSorted = hTimesetCloned.sort((a, b) => { + const dayDiff = a.weekday - b.weekday + if (dayDiff) + return dayDiff + else + return timeStrToMinutes(a.from) - timeStrToMinutes(b.from) + }) + + //combine, merge periods for a day. For example 0:00-2:00 and 1:00-4:00 should be merged into 0:00-4:00 + //Important: the timeset should be sorted by "weekday" and "from"! + const htNormalized = htSorted.reduce((acc, currentItem) => { + const prevItem = acc.pop() + if (!prevItem) { + acc.push(currentItem) + } + else if (prevItem.weekday !== currentItem.weekday) { + acc.push(prevItem) + acc.push(currentItem) + } + else { + const prevItemMinutes = { fromM: timeStrToMinutes(prevItem.from), toM: timeStrToMinutes(prevItem.to) } + const currentItemMinutes = { fromM: timeStrToMinutes(currentItem.from), toM: timeStrToMinutes(currentItem.to) } + + if (prevItemMinutes.fromM <= currentItemMinutes.fromM && currentItemMinutes.toM <= prevItemMinutes.toM) { + //current time range is completely inside previous time range --> just skipping current timeSet item + acc.push(prevItem) + } + else if (prevItemMinutes.toM < currentItemMinutes.fromM) { + //current time range is not part of previous time range --> adding both as separate time ranges + acc.push(prevItem) + acc.push(currentItem) + } + else if (prevItemMinutes.toM >= currentItemMinutes.fromM) { + //current time range is the next chunk\part of the previous time range + // OR they are intercepting --> extending\combining previous time range with current time range + prevItem.to = currentItem.to + acc.push(prevItem) + } + else { + console.info('Acc:', acc, 'prevItem:', prevItem, 'currentItem:', currentItem) + console.info('prevItemMinutes:', prevItemMinutes, 'currentItemMinutes:', currentItemMinutes) + throw Error('Internal error in "getHumanTimesetsNormalized"') + } + } + return acc + }, []) + + return htNormalized +} + +export function humanTimesetToKamailio(hTimeset = []) { + validateHumanTimesets(hTimeset) + + const htNormalized = getHumanTimesetsNormalized(hTimeset) + const kamailioTimesetRaw = htNormalized.map(timesetItem => { + const { weekday, from } = timesetItem + const to = timesetItem.to === '23:59' ? '24:00' : timesetItem.to + + const fromHM = getTimeStrElements(from) + const toHM = getTimeStrElements(to) + + const result = [] + if (fromHM.hours === toHM.hours) { + if (fromHM.minutes === toHM.minutes || fromHM.minutes + 1 === toHM.minutes) + result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}` }) + else + result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-${toHM.minutes-1}` }) + } + else { + //NOTE: any human readable time range for a day can be represented as next set of Kamailio ranges + //for example "1:10-5:35" can be represented as "1:10-2:00", "2:00-5:00", "5:00-5:35" and than coded in Kamailio format + // [ + // { wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` }, + // { wday: weekday, hour: `${fromHM.hours+1}-${toHM.hours-1}` }, + // { wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes-1}` } + // ] + //but code below will output a little more optimized version + + //"Starting" range + if (fromHM.minutes > 0) { + result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` }) + } + else fromHM.hours -= 1 + + //"Middle" range + if (fromHM.hours + 1 <= toHM.hours-1) { + result.push({ wday: weekday, hour: `${fromHM.hours + 1}-${toHM.hours - 1}` }) + } + + //"Ending" range + if (toHM.minutes > 0) { + result.push({ wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes-1}` }) + } + } + return result + }) + + const kamailioTimeset = kamailioTimesetRaw.reduce((acc, item) => { + const optimizedItemRanges = item.map(item => { + //if minute or hour contains range like "a-a" convert to just "a" + if (item.hour) { + let [hourF, hourT] = item.hour.split('-') + if (hourF === hourT) item.hour = hourF + } + + if (item.minute) { + let [minuteF, minuteT] = item.minute.split('-') + if (minuteF === minuteT) item.minute = minuteF + } + + return item + }) + //similar to flatMap + return [...acc, ...optimizedItemRanges] + }, []) + .reduce((acc, item) => { + //combine the same time ranges by "wday" + if (acc.length === 0) + acc.push(item) + else { + const mergeCandidate = acc.find(accItem => + accItem.hour === item.hour && + accItem.minute === item.minute && + getKamailioRangeElements(accItem.wday)[0].to + 1 === getKamailioRangeElements(item.wday)[0].from + ) + if (mergeCandidate) { + const mergeCandidateWday = getKamailioRangeElements(mergeCandidate.wday)[0] + mergeCandidate.wday = `${mergeCandidateWday.from}-${mergeCandidateWday.to + 1}` + } + else { + acc.push(item) + } + } + return acc + }, []) + + return kamailioTimeset +} + +//--------------- + +export function getKamailioRangeElements(kamailioRangeStr = '') { + const ranges = String(kamailioRangeStr).trim().split(' ').map(r => r.trim()).filter(r => r.length) + return ranges.map(r => { + const rangeElements = r.split('-').map(r => r.trim()).map(r => parseInt(r, 10)) + + if (rangeElements.length > 2) + throw Error('Invalid Kamailio range format: "' + kamailioRangeStr + '"') + + return { + from: rangeElements[0], + to: rangeElements.length === 2 ? rangeElements[1] : rangeElements[0] + } + }) +} + +export function validateKamailioRange(kamailioRangeStr = '', minValue, maxValue) { + const rangeElements = getKamailioRangeElements(kamailioRangeStr) + if (rangeElements.length === 0) + throw Error('Kamailio range should not be empty') + else if (rangeElements.length > 1) + throw Error('Kamailio multiple ranges are not supported: "' + kamailioRangeStr + '"') + else { + if (isNaN(rangeElements[0].from) || isNaN(rangeElements[0].to)) + throw Error('Kamailio range has invalid characters or a wrong format: "' + kamailioRangeStr + '"') + if (rangeElements[0].from > rangeElements[0].to) + throw Error('Kamailio reversed ranges are not supported: "' + kamailioRangeStr + '"') + if (minValue !== undefined && maxValue !== undefined) { + if (rangeElements[0].from < minValue || maxValue < rangeElements[0].from || + rangeElements[0].to < minValue || maxValue < rangeElements[0].to) { + throw Error(`Kamailio range elements are out of allowed values range (${minValue}..${maxValue}) : "${kamailioRangeStr}"`) + } + } + } +} + +export function validateKamailioTimesets(kTimeset) { + kTimeset.forEach(timesetItem => { + let { wday, hour, minute } = timesetItem + wday = (wday === null || wday === undefined) ? '' : String(wday).trim() + hour = (hour === null || hour === undefined) ? '' : String(hour).trim() + minute = (minute === null || minute === undefined) ? '' : String(minute).trim() + if (wday !== '') + validateKamailioRange(wday, 1, 7) + else + throw Error('A Kamailio timeset should have "wday" range: ' + JSON.stringify(timesetItem)) + if (hour !== '') + validateKamailioRange(hour, 0, 23) + if (minute !== '') + validateKamailioRange(minute, 0, 59) + + Object.entries(timesetItem) + .filter(([key]) => !['wday', 'hour', 'minute'].includes(key)) + .forEach(([key, value]) => { + if (!(value === null || value === undefined || String(value).trim().length === 0)) + throw Error(`The "${key}" scale of Kamailio timesets is not supported: ${JSON.stringify(timesetItem)}`) + }) + }) +} + +export function kamailioTimesetToHuman(kTimeset = []) { + validateKamailioTimesets(kTimeset) + + //convert Kamailio timeset into Human readable format + const hTimesetRaw = kTimeset.map(timesetItem => { + let { wday, hour, minute } = timesetItem + hour = (hour === null || hour === undefined) ? '' : String(hour).trim() + minute = (minute === null || minute === undefined) ? '' : String(minute).trim() + + wday = getKamailioRangeElements(wday)[0] + if (hour !== '') + hour = getKamailioRangeElements(hour)[0] + else + hour = { from: 0, to: 23 } + if (minute !== '') + minute = getKamailioRangeElements(minute)[0] + else + minute = { from: 0, to: 59 } + + const nestedRules = [wday, hour, minute].reverse() + const rulesOutput = nestedRules.reduce((acc, range) => { + const newAcc = [] + if (acc.length === 0) { + newAcc.push({ from: String(range.from), to: String(range.to + 1) }) + } + else { + for (let i = range.from; i <= range.to; i++) { + acc.forEach(accItem => + newAcc.push({ from: [i, accItem.from].join('_'), to: [i, accItem.to].join('_') }) + ) + } + } + return newAcc + }, []) + const hTimeset = rulesOutput.map(ruleOutput => { + const [fromWday, fromHour, fromMinute] = ruleOutput.from.split('_').map(i => Number(i)) + const [, toHour, toMinute] = ruleOutput.to.split('_').map(i => Number(i)) + const from = [fromHour, fromMinute] + .map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':') + const to = [ + (toMinute === 60) ? toHour + 1 : toHour, + (toMinute === 60) ? 0 : toMinute + ].map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':') + + return { + weekday: fromWday, + from, + to: (to === '24:00') ? '23:59': to + } + }) + + return hTimeset + }) + .reduce((acc, item) => [...acc, ...item], []) + + return getHumanTimesetsNormalized(hTimesetRaw) +} diff --git a/t/unit/kamailio-timesets-converter.js b/t/unit/kamailio-timesets-converter.js new file mode 100644 index 00000000..7d1627ee --- /dev/null +++ b/t/unit/kamailio-timesets-converter.js @@ -0,0 +1,373 @@ +'use strict'; + +import { assert } from 'chai'; +import _ from 'lodash'; +import { + getHumanTimesetsNormalized, + humanTimesetToKamailio, + kamailioTimesetToHuman, +} from '../../src/helpers/kamailio-timesets-converter' + +function deepEqualCheckAndInputDataMutationCheck(data, expectedResult, actionFn) { + const clonnedData = _.cloneDeep(data) + assert.deepEqual(actionFn(clonnedData), expectedResult) + assert.deepEqual(clonnedData, data, 'Original data were mutated') +} + +describe('function getHumanTimesetsNormalized', function() { + + it('case: resort', function(){ + + const options = [ + { weekday: 3, from: '9:00', to: '10:00' }, + { weekday: 2, from: '11:25', to: '15:00' }, + { weekday: 2, from: '9:12', to: '9:22' }, + { weekday: 2, from: '9:00', to: '9:10' } + ]; + const convertedData = [ + { weekday: 2, from: '9:00', to: '9:10' }, + { weekday: 2, from: '9:12', to: '9:22' }, + { weekday: 2, from: '11:25', to: '15:00' }, + { weekday: 3, from: '9:00', to: '10:00' } + ] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => getHumanTimesetsNormalized(data)) + }); + + it('case: combine chunks and remove duplicates', function(){ + + const options = [ + { weekday: 2, from: '9:00', to: '10:00' }, + { weekday: 2, from: '10:00', to: '12:00' }, + { weekday: 3, from: '11:25', to: '15:00' }, + { weekday: 3, from: '11:25', to: '15:00' }, + { weekday: 3, from: '9:00', to: '9:30' }, + { weekday: 3, from: '9:10', to: '11:30' }, + { weekday: 3, from: '10:00', to: '11:30' }, + { weekday: 3, from: '8:00', to: '8:59' } + ]; + const convertedData = [ + { weekday: 2, from: '9:00', to: '12:00' }, + { weekday: 3, from: '8:00', to: '8:59' }, + { weekday: 3, from: '9:00', to: '15:00' } + ] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => getHumanTimesetsNormalized(data)) + }); +}) + +describe('Human readable timesets to Kamailio format converter helpers', function() { + + it('case: an empty set', function(){ + + const options = []; + const convertedData = [] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + + const options2 = undefined; + const convertedData2 = [] + deepEqualCheckAndInputDataMutationCheck(options2, convertedData2, data => humanTimesetToKamailio(data)) + }); + + it('case: required field', function(){ + + let options = [{}]; + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1 }]; + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, from: '1:00' }]; + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, to: '1:00' }]; + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, from: '1:00', to: '2:00' }]; + assert.doesNotThrow(() => humanTimesetToKamailio(options)) + }); + + it('case: Mon 6:00-6:05', function(){ + + let options = [ + { weekday: 2, from: '6:00', to: '6:05' } + ]; + let convertedData = [ + { wday: 2, hour: '6', minute: '0-4' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Mon 14:05-15:00', function(){ + + let options = [ + { weekday: 2, from: '14:05', to: '15:00' } + ]; + let convertedData = [ + { wday: 2, hour: '14', minute: '5-59' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Mon 6:00-15:00', function(){ + + let options = [ + { weekday: 2, from: '6:00', to: '15:00' } + ]; + let convertedData = [ + { wday: 2, hour: '6-14' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Fri 3:02-21:41', function(){ + + let options = [ + { weekday: 6, from: '3:02', to: '21:41' } + ]; + let convertedData = [ + { wday: 6, hour: '3', minute: '2-59' }, + { wday: 6, hour: '4-20' }, + { wday: 6, hour: '21', minute: '0-40' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Mon 9:01-10:00, Mon 14:05-15:00, Mon 11:00-11:29', function(){ + + let options = [ + { weekday: 2, from: '9:01', to: '10:00' }, + { weekday: 2, from: '14:05', to: '15:00' }, + { weekday: 2, from: '11:00', to: '11:29' } + ]; + let convertedData = [ + { wday: 2, hour: '9', minute: '1-59' }, + { wday: 2, hour: '11', minute: '0-28' }, + { wday: 2, hour: '14', minute: '5-59' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Mon 0:00-23:59, Tue 0:00-23:58', function(){ + + //NOTE: 23:59 should be treated as 24:00 for TO field in interval + let options = [ + { weekday: 2, from: '0:00', to: '23:59' }, + { weekday: 3, from: '0:00', to: '23:58' } + ]; + let convertedData = [ + { wday: 2, hour: '0-23' }, + { wday: 3, hour: '0-22' }, + { wday: 3, hour: '23', minute: '0-57' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: automatic combining time intervals', function(){ + + let options = [ + { weekday: 3, from: '6:22', to: '7:44' }, + { weekday: 3, from: '5:10', to: '8:00' }, + { weekday: 3, from: '4:00', to: '7:00' }, + { weekday: 3, from: '8:30', to: '11:00' }, + { weekday: 3, from: '8:00', to: '9:30' }, + + { weekday: 2, from: '2:00', to: '20:00' }, + ]; + let convertedData = [ + { wday: 2, hour: '2-19' }, + { wday: 3, hour: '4-10' }, + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: wday range', function(){ + + let options = [ + { weekday: 6, from: '8:00', to: '17:00' }, + { weekday: 7, from: '8:00', to: '17:00' }, + + { weekday: 1, from: '8:00', to: '17:00' }, + + { weekday: 2, from: '8:00', to: '17:30' }, + { weekday: 3, from: '8:00', to: '17:30' }, + { weekday: 4, from: '8:00', to: '17:30' } + ]; + let convertedData = [ + { wday: '1-4', hour: '8-16' }, + { wday: '2-4', hour: '17', minute: '0-29' }, + { wday: '6-7', hour: '8-16' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + +}); + +describe('Kamailio format to Human readable timesets converter helpers', function() { + + it('case: an empty set', function(){ + + const options = []; + const convertedData = [] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + + const options2 = undefined; + const convertedData2 = [] + deepEqualCheckAndInputDataMutationCheck(options2, convertedData2, data => kamailioTimesetToHuman(data)) + }); + + it('case: required field', function(){ + + const options = [{}]; + assert.throws(() => kamailioTimesetToHuman(options)) + + const options2 = [{ wday: 1 }]; + assert.doesNotThrow(() => kamailioTimesetToHuman(options2)) + }); + + it('case: not supported fields', function(){ + + //There is not exception if we have empty not supported fields + let options = [ + { wday: 1, hour: '1', minute: '1', + second: '', month: '', year: null, week: '', yday: '', mday: '' } + ]; + + assert.doesNotThrow(() => kamailioTimesetToHuman(options)) + + //There should be an exception if we have a not empty not supported field + let options2 = [ + { weekday: 1, from: '1:00', to: '2:00', + second: '1', month: '1'} + ]; + + assert.throws(() => kamailioTimesetToHuman(options2)) + }); + + it('case: Mon 6:00-6:05', function(){ + + let options = [ + { wday: 2, hour: '6', minute: '0-4' } + ]; + let convertedData = [ + { weekday: 2, from: '6:00', to: '6:05' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: Mon 14:05-15:00', function(){ + + let options = [ + { wday: 2, hour: '14', minute: '5-59' } + ]; + let convertedData = [ + { weekday: 2, from: '14:05', to: '15:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: Mon 6:00-15:00', function(){ + let options = [ + { wday: 2, hour: '6-14' } + ]; + let convertedData = [ + { weekday: 2, from: '6:00', to: '15:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: Fri 3:02-21:41', function(){ + + let options = [ + { wday: 6, hour: '3', minute: '2-59' }, + { wday: 6, hour: '4-20' }, + { wday: 6, hour: '21', minute: '0-40' } + ]; + let convertedData = [ + { weekday: 6, from: '3:02', to: '21:41' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: Mon Mon 11:00-11:29, 9:01-10:00, Mon 14:05-15:00', function(){ + + let options = [ + { wday: 2, hour: '11', minute: '0-28' }, + { wday: 2, hour: '9', minute: '1-59' }, + { wday: 2, hour: '14', minute: '5-59' } + ]; + let convertedData = [ + { weekday: 2, from: '9:01', to: '10:00' }, + { weekday: 2, from: '11:00', to: '11:29' }, + { weekday: 2, from: '14:05', to: '15:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: Mon 0:00-23:59, Tue 0:00-23:58', function(){ + + //NOTE: 23:59 should be treated as 24:00 for TO field in interval + let options = [ + { wday: 2, hour: '0-23' }, + + { wday: 3, hour: '0-22' }, + { wday: 3, hour: '23', minute: '0-57' } + ]; + let convertedData = [ + { weekday: 2, from: '0:00', to: '23:59' }, + { weekday: 3, from: '0:00', to: '23:58' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: automatic combining time intervals', function(){ + + let options = [ + { wday: 7, hour: '9-13' }, + + { wday: 2, hour: '10-13' }, + { wday: 2, hour: '9', minute: '0-29' }, + { wday: 2, hour: '9', minute: '30-59' }, + { wday: 2, hour: '11-12', minute: '5-20' } + ]; + let convertedData = [ + { weekday: 2, from: '9:00', to: '14:00' }, + { weekday: 7, from: '9:00', to: '14:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); + + it('case: wday range', function(){ + + let options = [ + { wday: '1-4', hour: '8-16' }, + { wday: '2-4', hour: '17', minute: '0-29' }, + { wday: '6-7', hour: '8-16' } + ]; + let convertedData = [ + { weekday: 1, from: '8:00', to: '17:00' }, + + { weekday: 2, from: '8:00', to: '17:30' }, + { weekday: 3, from: '8:00', to: '17:30' }, + { weekday: 4, from: '8:00', to: '17:30' }, + + { weekday: 6, from: '8:00', to: '17:00' }, + { weekday: 7, from: '8:00', to: '17:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }); +}) diff --git a/test/jest/__tests__/helpers/kamailio-timesets-converter.spec.js b/test/jest/__tests__/helpers/kamailio-timesets-converter.spec.js new file mode 100644 index 00000000..f515ddb5 --- /dev/null +++ b/test/jest/__tests__/helpers/kamailio-timesets-converter.spec.js @@ -0,0 +1,377 @@ +/* eslint-disable */ + +import { assert } from 'chai' +import _ from 'lodash' +import { + getHumanTimesetsNormalized, + humanTimesetToKamailio, + kamailioTimesetToHuman +} from 'src/helpers/kamailio-timesets-converter' + +function deepEqualCheckAndInputDataMutationCheck(data, expectedResult, actionFn) { + const clonnedData = _.cloneDeep(data) + assert.deepEqual(actionFn(clonnedData), expectedResult) + assert.deepEqual(clonnedData, data, 'Original data were mutated') +} + +describe('function getHumanTimesetsNormalized', function () { + + it('case: resort', function () { + + const options = [ + { weekday: 3, from: '9:00', to: '10:00' }, + { weekday: 2, from: '11:25', to: '15:00' }, + { weekday: 2, from: '9:12', to: '9:22' }, + { weekday: 2, from: '9:00', to: '9:10' } + ] + const convertedData = [ + { weekday: 2, from: '9:00', to: '9:10' }, + { weekday: 2, from: '9:12', to: '9:22' }, + { weekday: 2, from: '11:25', to: '15:00' }, + { weekday: 3, from: '9:00', to: '10:00' } + ] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => getHumanTimesetsNormalized(data)) + }) + + it('case: combine chunks and remove duplicates', function () { + + const options = [ + { weekday: 2, from: '9:00', to: '10:00' }, + { weekday: 2, from: '10:00', to: '12:00' }, + { weekday: 3, from: '11:25', to: '15:00' }, + { weekday: 3, from: '11:25', to: '15:00' }, + { weekday: 3, from: '9:00', to: '9:30' }, + { weekday: 3, from: '9:10', to: '11:30' }, + { weekday: 3, from: '10:00', to: '11:30' }, + { weekday: 3, from: '8:00', to: '8:59' } + ] + const convertedData = [ + { weekday: 2, from: '9:00', to: '12:00' }, + { weekday: 3, from: '8:00', to: '8:59' }, + { weekday: 3, from: '9:00', to: '15:00' } + ] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => getHumanTimesetsNormalized(data)) + }); +}) + +describe('Human readable timesets to Kamailio format converter helpers', function () { + + it('case: an empty set', function () { + + const options = [] + const convertedData = [] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + + const options2 = undefined + const convertedData2 = [] + deepEqualCheckAndInputDataMutationCheck(options2, convertedData2, data => humanTimesetToKamailio(data)) + }); + + it('case: required field', function () { + + let options = [{}] + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1 }] + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, from: '1:00' }] + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, to: '1:00' }] + assert.throws(() => humanTimesetToKamailio(options)) + + options = [{ weekday: 1, from: '1:00', to: '2:00' }] + assert.doesNotThrow(() => humanTimesetToKamailio(options)) + }); + + it('case: Mon 6:00-6:05', function(){ + + let options = [ + { weekday: 2, from: '6:00', to: '6:05' } + ] + let convertedData = [ + { wday: 2, hour: '6', minute: '0-4' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: Mon 14:05-15:00', function(){ + + let options = [ + { weekday: 2, from: '14:05', to: '15:00' } + ] + let convertedData = [ + { wday: 2, hour: '14', minute: '5-59' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: Mon 6:00-15:00', function () { + + const options = [ + { weekday: 2, from: '6:00', to: '15:00' } + ] + const convertedData = [ + { wday: 2, hour: '6-14' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }); + + it('case: Fri 3:02-21:41', function(){ + + let options = [ + { weekday: 6, from: '3:02', to: '21:41' } + ]; + let convertedData = [ + { wday: 6, hour: '3', minute: '2-59' }, + { wday: 6, hour: '4-20' }, + { wday: 6, hour: '21', minute: '0-40' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: Mon 9:01-10:00, Mon 14:05-15:00, Mon 11:00-11:29', function () { + + const options = [ + { weekday: 2, from: '9:01', to: '10:00' }, + { weekday: 2, from: '14:05', to: '15:00' }, + { weekday: 2, from: '11:00', to: '11:29' } + ] + const convertedData = [ + { wday: 2, hour: '9', minute: '1-59' }, + { wday: 2, hour: '11', minute: '0-28' }, + { wday: 2, hour: '14', minute: '5-59' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: Mon 0:00-23:59, Tue 0:00-23:58', function () { + + // NOTE: 23:59 should be treated as 24:00 for TO field in interval + const options = [ + { weekday: 2, from: '0:00', to: '23:59' }, + { weekday: 3, from: '0:00', to: '23:58' } + ] + const convertedData = [ + { wday: 2, hour: '0-23' }, + { wday: 3, hour: '0-22' }, + { wday: 3, hour: '23', minute: '0-57' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: automatic combining time intervals', function () { + + const options = [ + { weekday: 3, from: '6:22', to: '7:44' }, + { weekday: 3, from: '5:10', to: '8:00' }, + { weekday: 3, from: '4:00', to: '7:00' }, + { weekday: 3, from: '8:30', to: '11:00' }, + { weekday: 3, from: '8:00', to: '9:30' }, + + { weekday: 2, from: '2:00', to: '20:00' } + ] + const convertedData = [ + { wday: 2, hour: '2-19' }, + { wday: 3, hour: '4-10' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + + it('case: wday range', function () { + + const options = [ + { weekday: 6, from: '8:00', to: '17:00' }, + { weekday: 7, from: '8:00', to: '17:00' }, + + { weekday: 1, from: '8:00', to: '17:00' }, + + { weekday: 2, from: '8:00', to: '17:30' }, + { weekday: 3, from: '8:00', to: '17:30' }, + { weekday: 4, from: '8:00', to: '17:30' } + ] + const convertedData = [ + { wday: '1-4', hour: '8-16' }, + { wday: '2-4', hour: '17', minute: '0-29' }, + { wday: '6-7', hour: '8-16' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => humanTimesetToKamailio(data)) + }) + +}) + +describe('Kamailio format to Human readable timesets converter helpers', function () { + + it('case: an empty set', function () { + + const options = [] + const convertedData = [] + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + + const options2 = undefined; + const convertedData2 = [] + deepEqualCheckAndInputDataMutationCheck(options2, convertedData2, data => kamailioTimesetToHuman(data)) + }) + + it('case: required field', function () { + + const options = [{}] + assert.throws(() => kamailioTimesetToHuman(options)) + + const options2 = [{ wday: 1 }] + assert.doesNotThrow(() => kamailioTimesetToHuman(options2)) + }) + + it('case: not supported fields', function () { + + // There is not exception if we have empty not supported fields + const options = [ + { + wday: 1, hour: '1', minute: '1', + second: '', month: '', year: null, week: '', yday: '', mday: '' + } + ] + + assert.doesNotThrow(() => kamailioTimesetToHuman(options)) + + // There should be an exception if we have a not empty not supported field + const options2 = [ + { + weekday: 1, from: '1:00', to: '2:00', + second: '1', month: '1' + } + ] + + assert.throws(() => kamailioTimesetToHuman(options2)) + }); + + it('case: Mon 6:00-6:05', function(){ + + let options = [ + { wday: 2, hour: '6', minute: '0-4' } + ] + let convertedData = [ + { weekday: 2, from: '6:00', to: '6:05' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: Mon 14:05-15:00', function(){ + + let options = [ + { wday: 2, hour: '14', minute: '5-59' } + ] + let convertedData = [ + { weekday: 2, from: '14:05', to: '15:00' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: Mon 6:00-15:00', function () { + const options = [ + { wday: 2, hour: '6-14' } + ] + const convertedData = [ + { weekday: 2, from: '6:00', to: '15:00' } + ]; + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: Fri 3:02-21:41', function(){ + + let options = [ + { wday: 6, hour: '3', minute: '2-59' }, + { wday: 6, hour: '4-20' }, + { wday: 6, hour: '21', minute: '0-40' } + ] + let convertedData = [ + { weekday: 6, from: '3:02', to: '21:41' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: Mon Mon 11:00-11:29, 9:01-10:00, Mon 14:05-15:00', function () { + + const options = [ + { wday: 2, hour: '11', minute: '0-28' }, + { wday: 2, hour: '9', minute: '1-59' }, + { wday: 2, hour: '14', minute: '5-59' } + ] + const convertedData = [ + { weekday: 2, from: '9:01', to: '10:00' }, + { weekday: 2, from: '11:00', to: '11:29' }, + { weekday: 2, from: '14:05', to: '15:00' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: Mon 0:00-23:59, Tue 0:00-23:58', function () { + + // NOTE: 23:59 should be treated as 24:00 for TO field in interval + const options = [ + { wday: 2, hour: '0-23' }, + + { wday: 3, hour: '0-22' }, + { wday: 3, hour: '23', minute: '0-57' } + ] + const convertedData = [ + { weekday: 2, from: '0:00', to: '23:59' }, + { weekday: 3, from: '0:00', to: '23:58' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: automatic combining time intervals', function () { + + const options = [ + { wday: 7, hour: '9-13' }, + + { wday: 2, hour: '10-13' }, + { wday: 2, hour: '9', minute: '0-29' }, + { wday: 2, hour: '9', minute: '30-59' }, + { wday: 2, hour: '11-12', minute: '5-20' } + ] + const convertedData = [ + { weekday: 2, from: '9:00', to: '14:00' }, + { weekday: 7, from: '9:00', to: '14:00' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) + + it('case: wday range', function () { + + const options = [ + { wday: '1-4', hour: '8-16' }, + { wday: '2-4', hour: '17', minute: '0-29' }, + { wday: '6-7', hour: '8-16' } + ] + const convertedData = [ + { weekday: 1, from: '8:00', to: '17:00' }, + + { weekday: 2, from: '8:00', to: '17:30' }, + { weekday: 3, from: '8:00', to: '17:30' }, + { weekday: 4, from: '8:00', to: '17:30' }, + + { weekday: 6, from: '8:00', to: '17:00' }, + { weekday: 7, from: '8:00', to: '17:00' } + ] + + deepEqualCheckAndInputDataMutationCheck(options, convertedData, data => kamailioTimesetToHuman(data)) + }) +})