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