perf: use mnemonist CirculerBuffer
[e-mobility-charging-stations-simulator.git] / tests / utils / Utils.test.ts
CommitLineData
fcda9151 1import { randomInt } from 'node:crypto'
8f801a93 2import { version } from 'node:process'
66a7748d 3import { describe, it } from 'node:test'
6c43b441 4
66a7748d
JB
5import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
6import { expect } from 'expect'
840ca85d 7import { CircularBuffer } from 'mnemonist'
8f801a93 8import { satisfies } from 'semver'
a2111e8d 9
840ca85d 10import type { TimestampedData } from '../../src/types/index.js'
66a7748d 11import { Constants } from '../../src/utils/Constants.js'
9bf0ef23 12import {
40615072 13 clone,
9bf0ef23
JB
14 convertToBoolean,
15 convertToDate,
16 convertToFloat,
17 convertToInt,
1553f6df 18 extractTimeSeriesValues,
be4c6702 19 formatDurationMilliSeconds,
8053c842 20 formatDurationSeconds,
9bf0ef23
JB
21 generateUUID,
22 getRandomFloat,
9bf0ef23 23 hasOwnProp,
80c58041 24 isArraySorted,
bcf95df1 25 isAsyncFunction,
9bf0ef23
JB
26 isNotEmptyArray,
27 isNotEmptyString,
9bf0ef23 28 isObject,
5dc7c990 29 isValidDate,
9bf0ef23
JB
30 roundTo,
31 secureRandom,
32 sleep,
66a7748d
JB
33 validateUUID
34} from '../../src/utils/Utils.js'
a2111e8d 35
6c43b441
JB
36await describe('Utils test suite', async () => {
37 await it('Verify generateUUID()/validateUUID()', () => {
66a7748d
JB
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)
878e026c 44 // Shall invalidate Nil UUID
66a7748d
JB
45 expect(validateUUID('00000000-0000-0000-0000-000000000000')).toBe(false)
46 expect(validateUUID('987FBC9-4BED-3078-CF07A-9141BA07C9F3')).toBe(false)
47 })
8bd02502 48
6c43b441 49 await it('Verify sleep()', async () => {
66a7748d
JB
50 const start = performance.now()
51 await sleep(1000)
52 const stop = performance.now()
53 expect(stop - start).toBeGreaterThanOrEqual(1000)
54 })
45999aab 55
6c43b441 56 await it('Verify formatDurationMilliSeconds()', () => {
66a7748d
JB
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 })
be4c6702 62
6c43b441 63 await it('Verify formatDurationSeconds()', () => {
66a7748d
JB
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 })
8053c842 69
40615072 70 await it('Verify isValidDate()', () => {
5dc7c990
JB
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)
66a7748d 78 })
ac8178a4 79
6c43b441 80 await it('Verify convertToDate()', () => {
66a7748d 81 expect(convertToDate(undefined)).toBe(undefined)
79fd697f 82 expect(convertToDate(null)).toBe(undefined)
66a7748d
JB
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 })
df645d8f 95
6c43b441 96 await it('Verify convertToInt()', () => {
66a7748d
JB
97 expect(convertToInt(undefined)).toBe(0)
98 expect(convertToInt(null)).toBe(0)
99 expect(convertToInt(0)).toBe(0)
fcda9151 100 const randomInteger = randomInt(Constants.MAX_RANDOM_INTEGER)
66a7748d
JB
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)
878e026c 112 expect(() => {
66a7748d
JB
113 convertToInt('NaN')
114 }).toThrow("Cannot convert to integer: 'NaN'")
115 })
df645d8f 116
6c43b441 117 await it('Verify convertToFloat()', () => {
66a7748d
JB
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)
878e026c 133 expect(() => {
66a7748d
JB
134 convertToFloat('NaN')
135 }).toThrow("Cannot convert to float: 'NaN'")
136 })
df645d8f 137
6c43b441 138 await it('Verify convertToBoolean()', () => {
66a7748d
JB
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 })
df645d8f 154
6c43b441 155 await it('Verify secureRandom()', () => {
66a7748d
JB
156 const random = secureRandom()
157 expect(typeof random === 'number').toBe(true)
158 expect(random).toBeGreaterThanOrEqual(0)
159 expect(random).toBeLessThan(1)
160 })
a2111e8d 161
6c43b441 162 await it('Verify roundTo()', () => {
66a7748d
JB
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 })
316d1564 176
6c43b441 177 await it('Verify getRandomFloat()', () => {
66a7748d
JB
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'))
68f56fdf 184 expect(() => getRandomFloat(Number.MAX_VALUE, -Number.MAX_VALUE)).toThrow(
66a7748d
JB
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 })
a2111e8d 191
6c43b441 192 await it('Verify extractTimeSeriesValues()', () => {
1553f6df 193 expect(
840ca85d
JB
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])
66a7748d 206 })
1553f6df 207
6c43b441 208 await it('Verify isObject()', () => {
66a7748d
JB
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 })
a2111e8d 224
bcf95df1
JB
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(new RegExp('[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
40615072 301 await it('Verify clone()', () => {
66a7748d 302 const obj = { 1: 1 }
40615072
JB
303 expect(clone(obj)).toStrictEqual(obj)
304 expect(clone(obj) === obj).toBe(false)
66a7748d 305 const nestedObj = { 1: obj, 2: obj }
40615072
JB
306 expect(clone(nestedObj)).toStrictEqual(nestedObj)
307 expect(clone(nestedObj) === nestedObj).toBe(false)
66a7748d 308 const array = [1, 2]
40615072
JB
309 expect(clone(array)).toStrictEqual(array)
310 expect(clone(array) === array).toBe(false)
66a7748d 311 const objArray = [obj, obj]
40615072
JB
312 expect(clone(objArray)).toStrictEqual(objArray)
313 expect(clone(objArray) === objArray).toBe(false)
66a7748d 314 const date = new Date()
40615072
JB
315 expect(clone(date)).toStrictEqual(date)
316 expect(clone(date) === date).toBe(false)
8f801a93
JB
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 }
66a7748d 321 const map = new Map([['1', '2']])
3fad0dec 322 expect(clone(map)).toStrictEqual(map)
cfab8dc9 323 expect(clone(map) === map).toBe(false)
66a7748d 324 const set = new Set(['1'])
3fad0dec 325 expect(clone(set)).toStrictEqual(set)
cfab8dc9 326 expect(clone(set) === set).toBe(false)
66a7748d 327 const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
8f801a93 328 expect(() => clone(weakMap)).toThrowError(new Error('#<WeakMap> could not be cloned.'))
66a7748d 329 const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
8f801a93 330 expect(() => clone(weakSet)).toThrowError(new Error('#<WeakSet> could not be cloned.'))
66a7748d 331 })
dd5a1d6c 332
6c43b441 333 await it('Verify hasOwnProp()', () => {
66a7748d
JB
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 })
af29b598 348
6c43b441 349 await it('Verify isNotEmptyString()', () => {
66a7748d
JB
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 })
429f8c9d 366
6c43b441 367 await it('Verify isNotEmptyArray()', () => {
66a7748d
JB
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 })
878e026c 382
6c43b441 383 await it('Verify isArraySorted()', () => {
80c58041
JB
384 expect(
385 isArraySorted([], (a, b) => {
66a7748d
JB
386 return a - b
387 })
388 ).toBe(true)
80c58041
JB
389 expect(
390 isArraySorted([1], (a, b) => {
66a7748d
JB
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 })
66a7748d 398})