refactor: convert home made helpers to rambda ones
[e-mobility-charging-stations-simulator.git] / tests / utils / Utils.test.ts
1 import { version } from 'node:process'
2 import { describe, it } from 'node:test'
3
4 import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
5 import { expect } from 'expect'
6 import { satisfies } from 'semver'
7
8 import { Constants } from '../../src/utils/Constants.js'
9 import {
10 clone,
11 convertToBoolean,
12 convertToDate,
13 convertToFloat,
14 convertToInt,
15 extractTimeSeriesValues,
16 formatDurationMilliSeconds,
17 formatDurationSeconds,
18 generateUUID,
19 getRandomFloat,
20 getRandomInteger,
21 hasOwnProp,
22 isArraySorted,
23 isAsyncFunction,
24 isNotEmptyArray,
25 isNotEmptyString,
26 isObject,
27 isValidDate,
28 max,
29 min,
30 roundTo,
31 secureRandom,
32 sleep,
33 validateUUID
34 } from '../../src/utils/Utils.js'
35
36 await describe('Utils test suite', async () => {
37 await it('Verify generateUUID()/validateUUID()', () => {
38 const uuid = generateUUID()
39 expect(uuid).toBeDefined()
40 expect(uuid.length).toEqual(36)
41 expect(validateUUID(uuid)).toBe(true)
42 expect(validateUUID('abcdef00-0000-4000-0000-000000000000')).toBe(true)
43 expect(validateUUID('')).toBe(false)
44 // Shall invalidate Nil UUID
45 expect(validateUUID('00000000-0000-0000-0000-000000000000')).toBe(false)
46 expect(validateUUID('987FBC9-4BED-3078-CF07A-9141BA07C9F3')).toBe(false)
47 })
48
49 await it('Verify sleep()', async () => {
50 const start = performance.now()
51 await sleep(1000)
52 const stop = performance.now()
53 expect(stop - start).toBeGreaterThanOrEqual(1000)
54 })
55
56 await it('Verify formatDurationMilliSeconds()', () => {
57 expect(formatDurationMilliSeconds(0)).toBe('0 seconds')
58 expect(formatDurationMilliSeconds(900)).toBe('0 seconds')
59 expect(formatDurationMilliSeconds(1000)).toBe('1 second')
60 expect(formatDurationMilliSeconds(hoursToMilliseconds(4380))).toBe('182 days 12 hours')
61 })
62
63 await it('Verify formatDurationSeconds()', () => {
64 expect(formatDurationSeconds(0)).toBe('0 seconds')
65 expect(formatDurationSeconds(0.9)).toBe('0 seconds')
66 expect(formatDurationSeconds(1)).toBe('1 second')
67 expect(formatDurationSeconds(hoursToSeconds(4380))).toBe('182 days 12 hours')
68 })
69
70 await it('Verify isValidDate()', () => {
71 expect(isValidDate(undefined)).toBe(false)
72 expect(isValidDate(-1)).toBe(true)
73 expect(isValidDate(0)).toBe(true)
74 expect(isValidDate(1)).toBe(true)
75 expect(isValidDate(-0.5)).toBe(true)
76 expect(isValidDate(0.5)).toBe(true)
77 expect(isValidDate(new Date())).toBe(true)
78 })
79
80 await it('Verify convertToDate()', () => {
81 expect(convertToDate(undefined)).toBe(undefined)
82 expect(convertToDate(null)).toBe(undefined)
83 expect(() => convertToDate('')).toThrow(new Error("Cannot convert to date: ''"))
84 expect(() => convertToDate('00:70:61')).toThrow(new Error("Cannot convert to date: '00:70:61'"))
85 expect(convertToDate(0)).toStrictEqual(new Date('1970-01-01T00:00:00.000Z'))
86 expect(convertToDate(-1)).toStrictEqual(new Date('1969-12-31T23:59:59.999Z'))
87 const dateStr = '2020-01-01T00:00:00.000Z'
88 let date = convertToDate(dateStr)
89 expect(date).toBeInstanceOf(Date)
90 expect(date).toStrictEqual(new Date(dateStr))
91 date = convertToDate(new Date(dateStr))
92 expect(date).toBeInstanceOf(Date)
93 expect(date).toStrictEqual(new Date(dateStr))
94 })
95
96 await it('Verify convertToInt()', () => {
97 expect(convertToInt(undefined)).toBe(0)
98 expect(convertToInt(null)).toBe(0)
99 expect(convertToInt(0)).toBe(0)
100 const randomInteger = getRandomInteger()
101 expect(convertToInt(randomInteger)).toEqual(randomInteger)
102 expect(convertToInt('-1')).toBe(-1)
103 expect(convertToInt('1')).toBe(1)
104 expect(convertToInt('1.1')).toBe(1)
105 expect(convertToInt('1.9')).toBe(1)
106 expect(convertToInt('1.999')).toBe(1)
107 expect(convertToInt(-1)).toBe(-1)
108 expect(convertToInt(1)).toBe(1)
109 expect(convertToInt(1.1)).toBe(1)
110 expect(convertToInt(1.9)).toBe(1)
111 expect(convertToInt(1.999)).toBe(1)
112 expect(() => {
113 convertToInt('NaN')
114 }).toThrow("Cannot convert to integer: 'NaN'")
115 })
116
117 await it('Verify convertToFloat()', () => {
118 expect(convertToFloat(undefined)).toBe(0)
119 expect(convertToFloat(null)).toBe(0)
120 expect(convertToFloat(0)).toBe(0)
121 const randomFloat = getRandomFloat()
122 expect(convertToFloat(randomFloat)).toEqual(randomFloat)
123 expect(convertToFloat('-1')).toBe(-1)
124 expect(convertToFloat('1')).toBe(1)
125 expect(convertToFloat('1.1')).toBe(1.1)
126 expect(convertToFloat('1.9')).toBe(1.9)
127 expect(convertToFloat('1.999')).toBe(1.999)
128 expect(convertToFloat(-1)).toBe(-1)
129 expect(convertToFloat(1)).toBe(1)
130 expect(convertToFloat(1.1)).toBe(1.1)
131 expect(convertToFloat(1.9)).toBe(1.9)
132 expect(convertToFloat(1.999)).toBe(1.999)
133 expect(() => {
134 convertToFloat('NaN')
135 }).toThrow("Cannot convert to float: 'NaN'")
136 })
137
138 await it('Verify convertToBoolean()', () => {
139 expect(convertToBoolean(undefined)).toBe(false)
140 expect(convertToBoolean(null)).toBe(false)
141 expect(convertToBoolean('true')).toBe(true)
142 expect(convertToBoolean('false')).toBe(false)
143 expect(convertToBoolean('TRUE')).toBe(true)
144 expect(convertToBoolean('FALSE')).toBe(false)
145 expect(convertToBoolean('1')).toBe(true)
146 expect(convertToBoolean('0')).toBe(false)
147 expect(convertToBoolean(1)).toBe(true)
148 expect(convertToBoolean(0)).toBe(false)
149 expect(convertToBoolean(true)).toBe(true)
150 expect(convertToBoolean(false)).toBe(false)
151 expect(convertToBoolean('')).toBe(false)
152 expect(convertToBoolean('NoNBoolean')).toBe(false)
153 })
154
155 await it('Verify secureRandom()', () => {
156 const random = secureRandom()
157 expect(typeof random === 'number').toBe(true)
158 expect(random).toBeGreaterThanOrEqual(0)
159 expect(random).toBeLessThan(1)
160 })
161
162 await it('Verify getRandomInteger()', () => {
163 let randomInteger = getRandomInteger()
164 expect(Number.isSafeInteger(randomInteger)).toBe(true)
165 expect(randomInteger).toBeGreaterThanOrEqual(0)
166 expect(randomInteger).toBeLessThanOrEqual(Constants.MAX_RANDOM_INTEGER)
167 expect(randomInteger).not.toEqual(getRandomInteger())
168 randomInteger = getRandomInteger(0, -Constants.MAX_RANDOM_INTEGER)
169 expect(randomInteger).toBeGreaterThanOrEqual(-Constants.MAX_RANDOM_INTEGER)
170 expect(randomInteger).toBeLessThanOrEqual(0)
171 expect(() => getRandomInteger(0, 1)).toThrow(
172 'The value of "max" is out of range. It must be greater than the value of "min" (1). Received 1'
173 )
174 expect(() => getRandomInteger(-1)).toThrow(
175 'The value of "max" is out of range. It must be greater than the value of "min" (0). Received 0'
176 )
177 expect(() => getRandomInteger(Constants.MAX_RANDOM_INTEGER + 1)).toThrow(
178 `The value of "max" is out of range. It must be <= ${
179 Constants.MAX_RANDOM_INTEGER + 1
180 }. Received 281_474_976_710_656`
181 )
182 randomInteger = getRandomInteger(2, 1)
183 expect(randomInteger).toBeGreaterThanOrEqual(1)
184 expect(randomInteger).toBeLessThanOrEqual(2)
185 const maximum = 2.2
186 const minimum = 1.1
187 randomInteger = getRandomInteger(maximum, minimum)
188 expect(randomInteger).toBeLessThanOrEqual(Math.floor(maximum))
189 expect(randomInteger).toBeGreaterThanOrEqual(Math.ceil(minimum))
190 })
191
192 await it('Verify roundTo()', () => {
193 expect(roundTo(0, 2)).toBe(0)
194 expect(roundTo(0.5, 0)).toBe(1)
195 expect(roundTo(0.5, 2)).toBe(0.5)
196 expect(roundTo(-0.5, 0)).toBe(-1)
197 expect(roundTo(-0.5, 2)).toBe(-0.5)
198 expect(roundTo(1.005, 0)).toBe(1)
199 expect(roundTo(1.005, 2)).toBe(1.01)
200 expect(roundTo(2.175, 2)).toBe(2.18)
201 expect(roundTo(5.015, 2)).toBe(5.02)
202 expect(roundTo(-1.005, 2)).toBe(-1.01)
203 expect(roundTo(-2.175, 2)).toBe(-2.18)
204 expect(roundTo(-5.015, 2)).toBe(-5.02)
205 })
206
207 await it('Verify getRandomFloat()', () => {
208 let randomFloat = getRandomFloat()
209 expect(typeof randomFloat === 'number').toBe(true)
210 expect(randomFloat).toBeGreaterThanOrEqual(0)
211 expect(randomFloat).toBeLessThanOrEqual(Number.MAX_VALUE)
212 expect(randomFloat).not.toEqual(getRandomFloat())
213 expect(() => getRandomFloat(0, 1)).toThrow(new RangeError('Invalid interval'))
214 expect(() => getRandomFloat(Number.MAX_VALUE, -Number.MAX_VALUE)).toThrow(
215 new RangeError('Invalid interval')
216 )
217 randomFloat = getRandomFloat(0, -Number.MAX_VALUE)
218 expect(randomFloat).toBeGreaterThanOrEqual(-Number.MAX_VALUE)
219 expect(randomFloat).toBeLessThanOrEqual(0)
220 })
221
222 await it('Verify extractTimeSeriesValues()', () => {
223 expect(extractTimeSeriesValues([])).toEqual([])
224 expect(extractTimeSeriesValues([{ timestamp: Date.now(), value: 1.1 }])).toEqual([1.1])
225 expect(
226 extractTimeSeriesValues([
227 { timestamp: Date.now(), value: 1.1 },
228 { timestamp: Date.now(), value: 2.2 }
229 ])
230 ).toEqual([1.1, 2.2])
231 })
232
233 await it('Verify isObject()', () => {
234 expect(isObject('test')).toBe(false)
235 expect(isObject(undefined)).toBe(false)
236 expect(isObject(null)).toBe(false)
237 expect(isObject(0)).toBe(false)
238 expect(isObject([])).toBe(false)
239 expect(isObject([0, 1])).toBe(false)
240 expect(isObject(['0', '1'])).toBe(false)
241 expect(isObject({})).toBe(true)
242 expect(isObject({ 1: 1 })).toBe(true)
243 expect(isObject({ 1: '1' })).toBe(true)
244 expect(isObject(new Map())).toBe(true)
245 expect(isObject(new Set())).toBe(true)
246 expect(isObject(new WeakMap())).toBe(true)
247 expect(isObject(new WeakSet())).toBe(true)
248 })
249
250 await it('Verify isAsyncFunction()', () => {
251 expect(isAsyncFunction(null)).toBe(false)
252 expect(isAsyncFunction(undefined)).toBe(false)
253 expect(isAsyncFunction(true)).toBe(false)
254 expect(isAsyncFunction(false)).toBe(false)
255 expect(isAsyncFunction(0)).toBe(false)
256 expect(isAsyncFunction('')).toBe(false)
257 expect(isAsyncFunction([])).toBe(false)
258 expect(isAsyncFunction(new Date())).toBe(false)
259 // eslint-disable-next-line prefer-regex-literals
260 expect(isAsyncFunction(new RegExp('[a-z]', 'i'))).toBe(false)
261 expect(isAsyncFunction(new Error())).toBe(false)
262 expect(isAsyncFunction(new Map())).toBe(false)
263 expect(isAsyncFunction(new Set())).toBe(false)
264 expect(isAsyncFunction(new WeakMap())).toBe(false)
265 expect(isAsyncFunction(new WeakSet())).toBe(false)
266 expect(isAsyncFunction(new Int8Array())).toBe(false)
267 expect(isAsyncFunction(new Uint8Array())).toBe(false)
268 expect(isAsyncFunction(new Uint8ClampedArray())).toBe(false)
269 expect(isAsyncFunction(new Int16Array())).toBe(false)
270 expect(isAsyncFunction(new Uint16Array())).toBe(false)
271 expect(isAsyncFunction(new Int32Array())).toBe(false)
272 expect(isAsyncFunction(new Uint32Array())).toBe(false)
273 expect(isAsyncFunction(new Float32Array())).toBe(false)
274 expect(isAsyncFunction(new Float64Array())).toBe(false)
275 expect(isAsyncFunction(new BigInt64Array())).toBe(false)
276 expect(isAsyncFunction(new BigUint64Array())).toBe(false)
277 // eslint-disable-next-line @typescript-eslint/no-empty-function
278 expect(isAsyncFunction(new Promise(() => {}))).toBe(false)
279 expect(isAsyncFunction(new WeakRef({}))).toBe(false)
280 // eslint-disable-next-line @typescript-eslint/no-empty-function
281 expect(isAsyncFunction(new FinalizationRegistry(() => {}))).toBe(false)
282 expect(isAsyncFunction(new ArrayBuffer(16))).toBe(false)
283 expect(isAsyncFunction(new SharedArrayBuffer(16))).toBe(false)
284 expect(isAsyncFunction(new DataView(new ArrayBuffer(16)))).toBe(false)
285 expect(isAsyncFunction({})).toBe(false)
286 expect(isAsyncFunction({ a: 1 })).toBe(false)
287 // eslint-disable-next-line @typescript-eslint/no-empty-function
288 expect(isAsyncFunction(() => {})).toBe(false)
289 // eslint-disable-next-line @typescript-eslint/no-empty-function
290 expect(isAsyncFunction(function () {})).toBe(false)
291 // eslint-disable-next-line @typescript-eslint/no-empty-function
292 expect(isAsyncFunction(function named () {})).toBe(false)
293 // eslint-disable-next-line @typescript-eslint/no-empty-function
294 expect(isAsyncFunction(async () => {})).toBe(true)
295 // eslint-disable-next-line @typescript-eslint/no-empty-function
296 expect(isAsyncFunction(async function () {})).toBe(true)
297 // eslint-disable-next-line @typescript-eslint/no-empty-function
298 expect(isAsyncFunction(async function named () {})).toBe(true)
299 class TestClass {
300 // eslint-disable-next-line @typescript-eslint/no-empty-function
301 public testSync (): void {}
302 // eslint-disable-next-line @typescript-eslint/no-empty-function
303 public async testAsync (): Promise<void> {}
304 // eslint-disable-next-line @typescript-eslint/no-empty-function
305 public testArrowSync = (): void => {}
306 // eslint-disable-next-line @typescript-eslint/no-empty-function
307 public testArrowAsync = async (): Promise<void> => {}
308 // eslint-disable-next-line @typescript-eslint/no-empty-function
309 public static testStaticSync (): void {}
310 // eslint-disable-next-line @typescript-eslint/no-empty-function
311 public static async testStaticAsync (): Promise<void> {}
312 }
313 const testClass = new TestClass()
314 // eslint-disable-next-line @typescript-eslint/unbound-method
315 expect(isAsyncFunction(testClass.testSync)).toBe(false)
316 // eslint-disable-next-line @typescript-eslint/unbound-method
317 expect(isAsyncFunction(testClass.testAsync)).toBe(true)
318 expect(isAsyncFunction(testClass.testArrowSync)).toBe(false)
319 expect(isAsyncFunction(testClass.testArrowAsync)).toBe(true)
320 // eslint-disable-next-line @typescript-eslint/unbound-method
321 expect(isAsyncFunction(TestClass.testStaticSync)).toBe(false)
322 // eslint-disable-next-line @typescript-eslint/unbound-method
323 expect(isAsyncFunction(TestClass.testStaticAsync)).toBe(true)
324 })
325
326 await it('Verify clone()', () => {
327 const obj = { 1: 1 }
328 expect(clone(obj)).toStrictEqual(obj)
329 expect(clone(obj) === obj).toBe(false)
330 const nestedObj = { 1: obj, 2: obj }
331 expect(clone(nestedObj)).toStrictEqual(nestedObj)
332 expect(clone(nestedObj) === nestedObj).toBe(false)
333 const array = [1, 2]
334 expect(clone(array)).toStrictEqual(array)
335 expect(clone(array) === array).toBe(false)
336 const objArray = [obj, obj]
337 expect(clone(objArray)).toStrictEqual(objArray)
338 expect(clone(objArray) === objArray).toBe(false)
339 const date = new Date()
340 expect(clone(date)).toStrictEqual(date)
341 expect(clone(date) === date).toBe(false)
342 if (satisfies(version, '>=21.0.0')) {
343 const url = new URL('https://domain.tld')
344 expect(() => clone(url)).toThrowError(new Error('Cannot clone object of unsupported type.'))
345 }
346 const map = new Map([['1', '2']])
347 expect(clone(map)).toStrictEqual(map)
348 expect(clone(map) === map).toBe(false)
349 const set = new Set(['1'])
350 expect(clone(set)).toStrictEqual(set)
351 expect(clone(set) === set).toBe(false)
352 const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
353 expect(() => clone(weakMap)).toThrowError(new Error('#<WeakMap> could not be cloned.'))
354 const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
355 expect(() => clone(weakSet)).toThrowError(new Error('#<WeakSet> could not be cloned.'))
356 })
357
358 await it('Verify hasOwnProp()', () => {
359 expect(hasOwnProp('test', '')).toBe(false)
360 expect(hasOwnProp(undefined, '')).toBe(false)
361 expect(hasOwnProp(null, '')).toBe(false)
362 expect(hasOwnProp([], '')).toBe(false)
363 expect(hasOwnProp({}, '')).toBe(false)
364 expect(hasOwnProp({ 1: 1 }, 1)).toBe(true)
365 expect(hasOwnProp({ 1: 1 }, '1')).toBe(true)
366 expect(hasOwnProp({ 1: 1 }, 2)).toBe(false)
367 expect(hasOwnProp({ 1: 1 }, '2')).toBe(false)
368 expect(hasOwnProp({ 1: '1' }, '1')).toBe(true)
369 expect(hasOwnProp({ 1: '1' }, 1)).toBe(true)
370 expect(hasOwnProp({ 1: '1' }, '2')).toBe(false)
371 expect(hasOwnProp({ 1: '1' }, 2)).toBe(false)
372 })
373
374 await it('Verify isNotEmptyString()', () => {
375 expect(isNotEmptyString('')).toBe(false)
376 expect(isNotEmptyString(' ')).toBe(false)
377 expect(isNotEmptyString(' ')).toBe(false)
378 expect(isNotEmptyString('test')).toBe(true)
379 expect(isNotEmptyString(' test')).toBe(true)
380 expect(isNotEmptyString('test ')).toBe(true)
381 expect(isNotEmptyString(undefined)).toBe(false)
382 expect(isNotEmptyString(null)).toBe(false)
383 expect(isNotEmptyString(0)).toBe(false)
384 expect(isNotEmptyString({})).toBe(false)
385 expect(isNotEmptyString([])).toBe(false)
386 expect(isNotEmptyString(new Map())).toBe(false)
387 expect(isNotEmptyString(new Set())).toBe(false)
388 expect(isNotEmptyString(new WeakMap())).toBe(false)
389 expect(isNotEmptyString(new WeakSet())).toBe(false)
390 })
391
392 await it('Verify isNotEmptyArray()', () => {
393 expect(isNotEmptyArray([])).toBe(false)
394 expect(isNotEmptyArray([1, 2])).toBe(true)
395 expect(isNotEmptyArray(['1', '2'])).toBe(true)
396 expect(isNotEmptyArray(undefined)).toBe(false)
397 expect(isNotEmptyArray(null)).toBe(false)
398 expect(isNotEmptyArray('')).toBe(false)
399 expect(isNotEmptyArray('test')).toBe(false)
400 expect(isNotEmptyArray(0)).toBe(false)
401 expect(isNotEmptyArray({})).toBe(false)
402 expect(isNotEmptyArray(new Map())).toBe(false)
403 expect(isNotEmptyArray(new Set())).toBe(false)
404 expect(isNotEmptyArray(new WeakMap())).toBe(false)
405 expect(isNotEmptyArray(new WeakSet())).toBe(false)
406 })
407
408 await it('Verify isArraySorted()', () => {
409 expect(
410 isArraySorted([], (a, b) => {
411 return a - b
412 })
413 ).toBe(true)
414 expect(
415 isArraySorted([1], (a, b) => {
416 return a - b
417 })
418 ).toBe(true)
419 expect(isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true)
420 expect(isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
421 expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
422 })
423
424 await it('Verify min()', () => {
425 expect(min()).toBe(Infinity)
426 expect(min(0, 1)).toBe(0)
427 expect(min(1, 0)).toBe(0)
428 expect(min(0, -1)).toBe(-1)
429 expect(min(-1, 0)).toBe(-1)
430 })
431
432 await it('Verify max()', () => {
433 expect(max()).toBe(-Infinity)
434 expect(max(0, 1)).toBe(1)
435 expect(max(1, 0)).toBe(1)
436 expect(max(0, -1)).toBe(0)
437 expect(max(-1, 0)).toBe(0)
438 })
439 })