build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / tests / utils / Utils.test.ts
1 import { randomInt } from 'node:crypto'
2 import { version } from 'node:process'
3 import { describe, it } from 'node:test'
4
5 import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
6 import { expect } from 'expect'
7 import { CircularBuffer } from 'mnemonist'
8 import { satisfies } from 'semver'
9
10 import type { TimestampedData } from '../../src/types/index.js'
11 import { Constants } from '../../src/utils/Constants.js'
12 import {
13 clone,
14 convertToBoolean,
15 convertToDate,
16 convertToFloat,
17 convertToInt,
18 extractTimeSeriesValues,
19 formatDurationMilliSeconds,
20 formatDurationSeconds,
21 generateUUID,
22 getRandomFloat,
23 hasOwnProp,
24 isArraySorted,
25 isAsyncFunction,
26 isNotEmptyArray,
27 isNotEmptyString,
28 isObject,
29 isValidDate,
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 = randomInt(Constants.MAX_RANDOM_INTEGER)
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 roundTo()', () => {
163 expect(roundTo(0, 2)).toBe(0)
164 expect(roundTo(0.5, 0)).toBe(1)
165 expect(roundTo(0.5, 2)).toBe(0.5)
166 expect(roundTo(-0.5, 0)).toBe(-1)
167 expect(roundTo(-0.5, 2)).toBe(-0.5)
168 expect(roundTo(1.005, 0)).toBe(1)
169 expect(roundTo(1.005, 2)).toBe(1.01)
170 expect(roundTo(2.175, 2)).toBe(2.18)
171 expect(roundTo(5.015, 2)).toBe(5.02)
172 expect(roundTo(-1.005, 2)).toBe(-1.01)
173 expect(roundTo(-2.175, 2)).toBe(-2.18)
174 expect(roundTo(-5.015, 2)).toBe(-5.02)
175 })
176
177 await it('Verify getRandomFloat()', () => {
178 let randomFloat = getRandomFloat()
179 expect(typeof randomFloat === 'number').toBe(true)
180 expect(randomFloat).toBeGreaterThanOrEqual(0)
181 expect(randomFloat).toBeLessThanOrEqual(Number.MAX_VALUE)
182 expect(randomFloat).not.toEqual(getRandomFloat())
183 expect(() => getRandomFloat(0, 1)).toThrow(new RangeError('Invalid interval'))
184 expect(() => getRandomFloat(Number.MAX_VALUE, -Number.MAX_VALUE)).toThrow(
185 new RangeError('Invalid interval')
186 )
187 randomFloat = getRandomFloat(0, -Number.MAX_VALUE)
188 expect(randomFloat).toBeGreaterThanOrEqual(-Number.MAX_VALUE)
189 expect(randomFloat).toBeLessThanOrEqual(0)
190 })
191
192 await it('Verify extractTimeSeriesValues()', () => {
193 expect(
194 extractTimeSeriesValues(
195 new CircularBuffer<TimestampedData>(Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY)
196 )
197 ).toEqual([])
198 const circularBuffer = new CircularBuffer<TimestampedData>(
199 Array,
200 Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY
201 )
202 circularBuffer.push({ timestamp: Date.now(), value: 1.1 })
203 circularBuffer.push({ timestamp: Date.now(), value: 2.2 })
204 circularBuffer.push({ timestamp: Date.now(), value: 3.3 })
205 expect(extractTimeSeriesValues(circularBuffer)).toEqual([1.1, 2.2, 3.3])
206 })
207
208 await it('Verify isObject()', () => {
209 expect(isObject('test')).toBe(false)
210 expect(isObject(undefined)).toBe(false)
211 expect(isObject(null)).toBe(false)
212 expect(isObject(0)).toBe(false)
213 expect(isObject([])).toBe(false)
214 expect(isObject([0, 1])).toBe(false)
215 expect(isObject(['0', '1'])).toBe(false)
216 expect(isObject({})).toBe(true)
217 expect(isObject({ 1: 1 })).toBe(true)
218 expect(isObject({ 1: '1' })).toBe(true)
219 expect(isObject(new Map())).toBe(true)
220 expect(isObject(new Set())).toBe(true)
221 expect(isObject(new WeakMap())).toBe(true)
222 expect(isObject(new WeakSet())).toBe(true)
223 })
224
225 await it('Verify isAsyncFunction()', () => {
226 expect(isAsyncFunction(null)).toBe(false)
227 expect(isAsyncFunction(undefined)).toBe(false)
228 expect(isAsyncFunction(true)).toBe(false)
229 expect(isAsyncFunction(false)).toBe(false)
230 expect(isAsyncFunction(0)).toBe(false)
231 expect(isAsyncFunction('')).toBe(false)
232 expect(isAsyncFunction([])).toBe(false)
233 expect(isAsyncFunction(new Date())).toBe(false)
234 // eslint-disable-next-line prefer-regex-literals
235 expect(isAsyncFunction(/[a-z]/i)).toBe(false)
236 expect(isAsyncFunction(new Error())).toBe(false)
237 expect(isAsyncFunction(new Map())).toBe(false)
238 expect(isAsyncFunction(new Set())).toBe(false)
239 expect(isAsyncFunction(new WeakMap())).toBe(false)
240 expect(isAsyncFunction(new WeakSet())).toBe(false)
241 expect(isAsyncFunction(new Int8Array())).toBe(false)
242 expect(isAsyncFunction(new Uint8Array())).toBe(false)
243 expect(isAsyncFunction(new Uint8ClampedArray())).toBe(false)
244 expect(isAsyncFunction(new Int16Array())).toBe(false)
245 expect(isAsyncFunction(new Uint16Array())).toBe(false)
246 expect(isAsyncFunction(new Int32Array())).toBe(false)
247 expect(isAsyncFunction(new Uint32Array())).toBe(false)
248 expect(isAsyncFunction(new Float32Array())).toBe(false)
249 expect(isAsyncFunction(new Float64Array())).toBe(false)
250 expect(isAsyncFunction(new BigInt64Array())).toBe(false)
251 expect(isAsyncFunction(new BigUint64Array())).toBe(false)
252 // eslint-disable-next-line @typescript-eslint/no-empty-function
253 expect(isAsyncFunction(new Promise(() => {}))).toBe(false)
254 expect(isAsyncFunction(new WeakRef({}))).toBe(false)
255 // eslint-disable-next-line @typescript-eslint/no-empty-function
256 expect(isAsyncFunction(new FinalizationRegistry(() => {}))).toBe(false)
257 expect(isAsyncFunction(new ArrayBuffer(16))).toBe(false)
258 expect(isAsyncFunction(new SharedArrayBuffer(16))).toBe(false)
259 expect(isAsyncFunction(new DataView(new ArrayBuffer(16)))).toBe(false)
260 expect(isAsyncFunction({})).toBe(false)
261 expect(isAsyncFunction({ a: 1 })).toBe(false)
262 // eslint-disable-next-line @typescript-eslint/no-empty-function
263 expect(isAsyncFunction(() => {})).toBe(false)
264 // eslint-disable-next-line @typescript-eslint/no-empty-function
265 expect(isAsyncFunction(function () {})).toBe(false)
266 // eslint-disable-next-line @typescript-eslint/no-empty-function
267 expect(isAsyncFunction(function named () {})).toBe(false)
268 // eslint-disable-next-line @typescript-eslint/no-empty-function
269 expect(isAsyncFunction(async () => {})).toBe(true)
270 // eslint-disable-next-line @typescript-eslint/no-empty-function
271 expect(isAsyncFunction(async function () {})).toBe(true)
272 // eslint-disable-next-line @typescript-eslint/no-empty-function
273 expect(isAsyncFunction(async function named () {})).toBe(true)
274 class TestClass {
275 // eslint-disable-next-line @typescript-eslint/no-empty-function
276 public testSync (): void {}
277 // eslint-disable-next-line @typescript-eslint/no-empty-function
278 public async testAsync (): Promise<void> {}
279 // eslint-disable-next-line @typescript-eslint/no-empty-function
280 public testArrowSync = (): void => {}
281 // eslint-disable-next-line @typescript-eslint/no-empty-function
282 public testArrowAsync = async (): Promise<void> => {}
283 // eslint-disable-next-line @typescript-eslint/no-empty-function
284 public static testStaticSync (): void {}
285 // eslint-disable-next-line @typescript-eslint/no-empty-function
286 public static async testStaticAsync (): Promise<void> {}
287 }
288 const testClass = new TestClass()
289 // eslint-disable-next-line @typescript-eslint/unbound-method
290 expect(isAsyncFunction(testClass.testSync)).toBe(false)
291 // eslint-disable-next-line @typescript-eslint/unbound-method
292 expect(isAsyncFunction(testClass.testAsync)).toBe(true)
293 expect(isAsyncFunction(testClass.testArrowSync)).toBe(false)
294 expect(isAsyncFunction(testClass.testArrowAsync)).toBe(true)
295 // eslint-disable-next-line @typescript-eslint/unbound-method
296 expect(isAsyncFunction(TestClass.testStaticSync)).toBe(false)
297 // eslint-disable-next-line @typescript-eslint/unbound-method
298 expect(isAsyncFunction(TestClass.testStaticAsync)).toBe(true)
299 })
300
301 await it('Verify clone()', () => {
302 const obj = { 1: 1 }
303 expect(clone(obj)).toStrictEqual(obj)
304 expect(clone(obj) === obj).toBe(false)
305 const nestedObj = { 1: obj, 2: obj }
306 expect(clone(nestedObj)).toStrictEqual(nestedObj)
307 expect(clone(nestedObj) === nestedObj).toBe(false)
308 const array = [1, 2]
309 expect(clone(array)).toStrictEqual(array)
310 expect(clone(array) === array).toBe(false)
311 const objArray = [obj, obj]
312 expect(clone(objArray)).toStrictEqual(objArray)
313 expect(clone(objArray) === objArray).toBe(false)
314 const date = new Date()
315 expect(clone(date)).toStrictEqual(date)
316 expect(clone(date) === date).toBe(false)
317 if (satisfies(version, '>=21.0.0')) {
318 const url = new URL('https://domain.tld')
319 expect(() => clone(url)).toThrowError(new Error('Cannot clone object of unsupported type.'))
320 }
321 const map = new Map([['1', '2']])
322 expect(clone(map)).toStrictEqual(map)
323 expect(clone(map) === map).toBe(false)
324 const set = new Set(['1'])
325 expect(clone(set)).toStrictEqual(set)
326 expect(clone(set) === set).toBe(false)
327 const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
328 expect(() => clone(weakMap)).toThrowError(new Error('#<WeakMap> could not be cloned.'))
329 const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
330 expect(() => clone(weakSet)).toThrowError(new Error('#<WeakSet> could not be cloned.'))
331 })
332
333 await it('Verify hasOwnProp()', () => {
334 expect(hasOwnProp('test', '')).toBe(false)
335 expect(hasOwnProp(undefined, '')).toBe(false)
336 expect(hasOwnProp(null, '')).toBe(false)
337 expect(hasOwnProp([], '')).toBe(false)
338 expect(hasOwnProp({}, '')).toBe(false)
339 expect(hasOwnProp({ 1: 1 }, 1)).toBe(true)
340 expect(hasOwnProp({ 1: 1 }, '1')).toBe(true)
341 expect(hasOwnProp({ 1: 1 }, 2)).toBe(false)
342 expect(hasOwnProp({ 1: 1 }, '2')).toBe(false)
343 expect(hasOwnProp({ 1: '1' }, '1')).toBe(true)
344 expect(hasOwnProp({ 1: '1' }, 1)).toBe(true)
345 expect(hasOwnProp({ 1: '1' }, '2')).toBe(false)
346 expect(hasOwnProp({ 1: '1' }, 2)).toBe(false)
347 })
348
349 await it('Verify isNotEmptyString()', () => {
350 expect(isNotEmptyString('')).toBe(false)
351 expect(isNotEmptyString(' ')).toBe(false)
352 expect(isNotEmptyString(' ')).toBe(false)
353 expect(isNotEmptyString('test')).toBe(true)
354 expect(isNotEmptyString(' test')).toBe(true)
355 expect(isNotEmptyString('test ')).toBe(true)
356 expect(isNotEmptyString(undefined)).toBe(false)
357 expect(isNotEmptyString(null)).toBe(false)
358 expect(isNotEmptyString(0)).toBe(false)
359 expect(isNotEmptyString({})).toBe(false)
360 expect(isNotEmptyString([])).toBe(false)
361 expect(isNotEmptyString(new Map())).toBe(false)
362 expect(isNotEmptyString(new Set())).toBe(false)
363 expect(isNotEmptyString(new WeakMap())).toBe(false)
364 expect(isNotEmptyString(new WeakSet())).toBe(false)
365 })
366
367 await it('Verify isNotEmptyArray()', () => {
368 expect(isNotEmptyArray([])).toBe(false)
369 expect(isNotEmptyArray([1, 2])).toBe(true)
370 expect(isNotEmptyArray(['1', '2'])).toBe(true)
371 expect(isNotEmptyArray(undefined)).toBe(false)
372 expect(isNotEmptyArray(null)).toBe(false)
373 expect(isNotEmptyArray('')).toBe(false)
374 expect(isNotEmptyArray('test')).toBe(false)
375 expect(isNotEmptyArray(0)).toBe(false)
376 expect(isNotEmptyArray({})).toBe(false)
377 expect(isNotEmptyArray(new Map())).toBe(false)
378 expect(isNotEmptyArray(new Set())).toBe(false)
379 expect(isNotEmptyArray(new WeakMap())).toBe(false)
380 expect(isNotEmptyArray(new WeakSet())).toBe(false)
381 })
382
383 await it('Verify isArraySorted()', () => {
384 expect(
385 isArraySorted([], (a, b) => {
386 return a - b
387 })
388 ).toBe(true)
389 expect(
390 isArraySorted([1], (a, b) => {
391 return a - b
392 })
393 ).toBe(true)
394 expect(isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true)
395 expect(isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
396 expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
397 })
398 })