Açıklama Yok

Utils.test.ts 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import { expect } from '@std/expect'
  2. import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
  3. import { CircularBuffer } from 'mnemonist'
  4. import { randomInt } from 'node:crypto'
  5. import { version } from 'node:process'
  6. import { describe, it } from 'node:test'
  7. import { satisfies } from 'semver'
  8. import type { TimestampedData } from '../../src/types/index.js'
  9. import { JSRuntime, runtime } from '../../scripts/runtime.js'
  10. import { Constants } from '../../src/utils/Constants.js'
  11. import {
  12. clone,
  13. convertToBoolean,
  14. convertToDate,
  15. convertToFloat,
  16. convertToInt,
  17. extractTimeSeriesValues,
  18. formatDurationMilliSeconds,
  19. formatDurationSeconds,
  20. generateUUID,
  21. getRandomFloat,
  22. insertAt,
  23. isArraySorted,
  24. isAsyncFunction,
  25. isNotEmptyArray,
  26. isNotEmptyString,
  27. isValidDate,
  28. roundTo,
  29. secureRandom,
  30. sleep,
  31. validateUUID,
  32. } from '../../src/utils/Utils.js'
  33. await describe('Utils test suite', async () => {
  34. await it('Verify generateUUID()/validateUUID()', () => {
  35. const uuid = generateUUID()
  36. expect(uuid).toBeDefined()
  37. expect(uuid.length).toEqual(36)
  38. expect(validateUUID(uuid)).toBe(true)
  39. expect(validateUUID('abcdef00-0000-4000-0000-000000000000')).toBe(true)
  40. expect(validateUUID('')).toBe(false)
  41. // Shall invalidate Nil UUID
  42. expect(validateUUID('00000000-0000-0000-0000-000000000000')).toBe(false)
  43. expect(validateUUID('987FBC9-4BED-3078-CF07A-9141BA07C9F3')).toBe(false)
  44. })
  45. await it('Verify sleep()', async () => {
  46. const start = performance.now()
  47. await sleep(1000)
  48. const stop = performance.now()
  49. expect(stop - start).toBeGreaterThanOrEqual(1000)
  50. })
  51. await it('Verify formatDurationMilliSeconds()', () => {
  52. expect(formatDurationMilliSeconds(0)).toBe('0 seconds')
  53. expect(formatDurationMilliSeconds(900)).toBe('0 seconds')
  54. expect(formatDurationMilliSeconds(1000)).toBe('1 second')
  55. expect(formatDurationMilliSeconds(hoursToMilliseconds(4380))).toBe('182 days 12 hours')
  56. })
  57. await it('Verify formatDurationSeconds()', () => {
  58. expect(formatDurationSeconds(0)).toBe('0 seconds')
  59. expect(formatDurationSeconds(0.9)).toBe('0 seconds')
  60. expect(formatDurationSeconds(1)).toBe('1 second')
  61. expect(formatDurationSeconds(hoursToSeconds(4380))).toBe('182 days 12 hours')
  62. })
  63. await it('Verify isValidDate()', () => {
  64. expect(isValidDate(undefined)).toBe(false)
  65. expect(isValidDate(-1)).toBe(true)
  66. expect(isValidDate(0)).toBe(true)
  67. expect(isValidDate(1)).toBe(true)
  68. expect(isValidDate(-0.5)).toBe(true)
  69. expect(isValidDate(0.5)).toBe(true)
  70. expect(isValidDate(new Date())).toBe(true)
  71. })
  72. await it('Verify convertToDate()', () => {
  73. expect(convertToDate(undefined)).toBe(undefined)
  74. expect(convertToDate(null)).toBe(undefined)
  75. expect(() => convertToDate('')).toThrow(new Error("Cannot convert to date: ''"))
  76. expect(() => convertToDate('00:70:61')).toThrow(new Error("Cannot convert to date: '00:70:61'"))
  77. expect(convertToDate(0)).toStrictEqual(new Date('1970-01-01T00:00:00.000Z'))
  78. expect(convertToDate(-1)).toStrictEqual(new Date('1969-12-31T23:59:59.999Z'))
  79. const dateStr = '2020-01-01T00:00:00.000Z'
  80. let date = convertToDate(dateStr)
  81. expect(date).toBeInstanceOf(Date)
  82. expect(date).toStrictEqual(new Date(dateStr))
  83. date = convertToDate(new Date(dateStr))
  84. expect(date).toBeInstanceOf(Date)
  85. expect(date).toStrictEqual(new Date(dateStr))
  86. })
  87. await it('Verify convertToInt()', () => {
  88. expect(convertToInt(undefined)).toBe(0)
  89. expect(convertToInt(null)).toBe(0)
  90. expect(convertToInt(0)).toBe(0)
  91. const randomInteger = randomInt(Constants.MAX_RANDOM_INTEGER)
  92. expect(convertToInt(randomInteger)).toEqual(randomInteger)
  93. expect(convertToInt('-1')).toBe(-1)
  94. expect(convertToInt('1')).toBe(1)
  95. expect(convertToInt('1.1')).toBe(1)
  96. expect(convertToInt('1.9')).toBe(1)
  97. expect(convertToInt('1.999')).toBe(1)
  98. expect(convertToInt(-1)).toBe(-1)
  99. expect(convertToInt(1)).toBe(1)
  100. expect(convertToInt(1.1)).toBe(1)
  101. expect(convertToInt(1.9)).toBe(1)
  102. expect(convertToInt(1.999)).toBe(1)
  103. expect(() => {
  104. convertToInt('NaN')
  105. }).toThrow("Cannot convert to integer: 'NaN'")
  106. })
  107. await it('Verify convertToFloat()', () => {
  108. expect(convertToFloat(undefined)).toBe(0)
  109. expect(convertToFloat(null)).toBe(0)
  110. expect(convertToFloat(0)).toBe(0)
  111. const randomFloat = getRandomFloat()
  112. expect(convertToFloat(randomFloat)).toEqual(randomFloat)
  113. expect(convertToFloat('-1')).toBe(-1)
  114. expect(convertToFloat('1')).toBe(1)
  115. expect(convertToFloat('1.1')).toBe(1.1)
  116. expect(convertToFloat('1.9')).toBe(1.9)
  117. expect(convertToFloat('1.999')).toBe(1.999)
  118. expect(convertToFloat(-1)).toBe(-1)
  119. expect(convertToFloat(1)).toBe(1)
  120. expect(convertToFloat(1.1)).toBe(1.1)
  121. expect(convertToFloat(1.9)).toBe(1.9)
  122. expect(convertToFloat(1.999)).toBe(1.999)
  123. expect(() => {
  124. convertToFloat('NaN')
  125. }).toThrow("Cannot convert to float: 'NaN'")
  126. })
  127. await it('Verify convertToBoolean()', () => {
  128. expect(convertToBoolean(undefined)).toBe(false)
  129. expect(convertToBoolean(null)).toBe(false)
  130. expect(convertToBoolean('true')).toBe(true)
  131. expect(convertToBoolean('false')).toBe(false)
  132. expect(convertToBoolean('TRUE')).toBe(true)
  133. expect(convertToBoolean('FALSE')).toBe(false)
  134. expect(convertToBoolean('1')).toBe(true)
  135. expect(convertToBoolean('0')).toBe(false)
  136. expect(convertToBoolean(1)).toBe(true)
  137. expect(convertToBoolean(0)).toBe(false)
  138. expect(convertToBoolean(true)).toBe(true)
  139. expect(convertToBoolean(false)).toBe(false)
  140. expect(convertToBoolean('')).toBe(false)
  141. expect(convertToBoolean('NoNBoolean')).toBe(false)
  142. })
  143. await it('Verify secureRandom()', () => {
  144. const random = secureRandom()
  145. expect(typeof random === 'number').toBe(true)
  146. expect(random).toBeGreaterThanOrEqual(0)
  147. expect(random).toBeLessThan(1)
  148. })
  149. await it('Verify roundTo()', () => {
  150. expect(roundTo(0, 2)).toBe(0)
  151. expect(roundTo(0.5, 0)).toBe(1)
  152. expect(roundTo(0.5, 2)).toBe(0.5)
  153. expect(roundTo(-0.5, 0)).toBe(-1)
  154. expect(roundTo(-0.5, 2)).toBe(-0.5)
  155. expect(roundTo(1.005, 0)).toBe(1)
  156. expect(roundTo(1.005, 2)).toBe(1.01)
  157. expect(roundTo(2.175, 2)).toBe(2.18)
  158. expect(roundTo(5.015, 2)).toBe(5.02)
  159. expect(roundTo(-1.005, 2)).toBe(-1.01)
  160. expect(roundTo(-2.175, 2)).toBe(-2.18)
  161. expect(roundTo(-5.015, 2)).toBe(-5.02)
  162. })
  163. await it('Verify getRandomFloat()', () => {
  164. let randomFloat = getRandomFloat()
  165. expect(typeof randomFloat === 'number').toBe(true)
  166. expect(randomFloat).toBeGreaterThanOrEqual(0)
  167. expect(randomFloat).toBeLessThanOrEqual(Number.MAX_VALUE)
  168. expect(randomFloat).not.toEqual(getRandomFloat())
  169. expect(() => getRandomFloat(0, 1)).toThrow(new RangeError('Invalid interval'))
  170. expect(() => getRandomFloat(Number.MAX_VALUE, -Number.MAX_VALUE)).toThrow(
  171. new RangeError('Invalid interval')
  172. )
  173. randomFloat = getRandomFloat(0, -Number.MAX_VALUE)
  174. expect(randomFloat).toBeGreaterThanOrEqual(-Number.MAX_VALUE)
  175. expect(randomFloat).toBeLessThanOrEqual(0)
  176. })
  177. await it('Verify extractTimeSeriesValues()', () => {
  178. expect(
  179. extractTimeSeriesValues(
  180. new CircularBuffer<TimestampedData>(Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY)
  181. )
  182. ).toEqual([])
  183. const circularBuffer = new CircularBuffer<TimestampedData>(
  184. Array,
  185. Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY
  186. )
  187. circularBuffer.push({ timestamp: Date.now(), value: 1.1 })
  188. circularBuffer.push({ timestamp: Date.now(), value: 2.2 })
  189. circularBuffer.push({ timestamp: Date.now(), value: 3.3 })
  190. expect(extractTimeSeriesValues(circularBuffer)).toEqual([1.1, 2.2, 3.3])
  191. })
  192. await it('Verify isAsyncFunction()', () => {
  193. expect(isAsyncFunction(null)).toBe(false)
  194. expect(isAsyncFunction(undefined)).toBe(false)
  195. expect(isAsyncFunction(true)).toBe(false)
  196. expect(isAsyncFunction(false)).toBe(false)
  197. expect(isAsyncFunction(0)).toBe(false)
  198. expect(isAsyncFunction('')).toBe(false)
  199. expect(isAsyncFunction([])).toBe(false)
  200. expect(isAsyncFunction(new Date())).toBe(false)
  201. expect(isAsyncFunction(/[a-z]/i)).toBe(false)
  202. expect(isAsyncFunction(new Error())).toBe(false)
  203. expect(isAsyncFunction(new Map())).toBe(false)
  204. expect(isAsyncFunction(new Set())).toBe(false)
  205. expect(isAsyncFunction(new WeakMap())).toBe(false)
  206. expect(isAsyncFunction(new WeakSet())).toBe(false)
  207. expect(isAsyncFunction(new Int8Array())).toBe(false)
  208. expect(isAsyncFunction(new Uint8Array())).toBe(false)
  209. expect(isAsyncFunction(new Uint8ClampedArray())).toBe(false)
  210. expect(isAsyncFunction(new Int16Array())).toBe(false)
  211. expect(isAsyncFunction(new Uint16Array())).toBe(false)
  212. expect(isAsyncFunction(new Int32Array())).toBe(false)
  213. expect(isAsyncFunction(new Uint32Array())).toBe(false)
  214. expect(isAsyncFunction(new Float32Array())).toBe(false)
  215. expect(isAsyncFunction(new Float64Array())).toBe(false)
  216. expect(isAsyncFunction(new BigInt64Array())).toBe(false)
  217. expect(isAsyncFunction(new BigUint64Array())).toBe(false)
  218. // eslint-disable-next-line @typescript-eslint/no-empty-function
  219. expect(isAsyncFunction(new Promise(() => {}))).toBe(false)
  220. expect(isAsyncFunction(new WeakRef({}))).toBe(false)
  221. // eslint-disable-next-line @typescript-eslint/no-empty-function
  222. expect(isAsyncFunction(new FinalizationRegistry(() => {}))).toBe(false)
  223. expect(isAsyncFunction(new ArrayBuffer(16))).toBe(false)
  224. expect(isAsyncFunction(new SharedArrayBuffer(16))).toBe(false)
  225. expect(isAsyncFunction(new DataView(new ArrayBuffer(16)))).toBe(false)
  226. expect(isAsyncFunction({})).toBe(false)
  227. expect(isAsyncFunction({ a: 1 })).toBe(false)
  228. // eslint-disable-next-line @typescript-eslint/no-empty-function
  229. expect(isAsyncFunction(() => {})).toBe(false)
  230. // eslint-disable-next-line @typescript-eslint/no-empty-function
  231. expect(isAsyncFunction(function () {})).toBe(false)
  232. // eslint-disable-next-line @typescript-eslint/no-empty-function
  233. expect(isAsyncFunction(function named () {})).toBe(false)
  234. // eslint-disable-next-line @typescript-eslint/no-empty-function
  235. expect(isAsyncFunction(async () => {})).toBe(true)
  236. // eslint-disable-next-line @typescript-eslint/no-empty-function
  237. expect(isAsyncFunction(async function () {})).toBe(true)
  238. // eslint-disable-next-line @typescript-eslint/no-empty-function
  239. expect(isAsyncFunction(async function named () {})).toBe(true)
  240. class TestClass {
  241. // eslint-disable-next-line @typescript-eslint/no-empty-function
  242. public static async testStaticAsync (): Promise<void> {}
  243. // eslint-disable-next-line @typescript-eslint/no-empty-function
  244. public static testStaticSync (): void {}
  245. // eslint-disable-next-line @typescript-eslint/no-empty-function
  246. public testArrowAsync = async (): Promise<void> => {}
  247. // eslint-disable-next-line @typescript-eslint/no-empty-function
  248. public testArrowSync = (): void => {}
  249. // eslint-disable-next-line @typescript-eslint/no-empty-function
  250. public async testAsync (): Promise<void> {}
  251. // eslint-disable-next-line @typescript-eslint/no-empty-function
  252. public testSync (): void {}
  253. }
  254. const testClass = new TestClass()
  255. // eslint-disable-next-line @typescript-eslint/unbound-method
  256. expect(isAsyncFunction(testClass.testSync)).toBe(false)
  257. // eslint-disable-next-line @typescript-eslint/unbound-method
  258. expect(isAsyncFunction(testClass.testAsync)).toBe(true)
  259. expect(isAsyncFunction(testClass.testArrowSync)).toBe(false)
  260. expect(isAsyncFunction(testClass.testArrowAsync)).toBe(true)
  261. // eslint-disable-next-line @typescript-eslint/unbound-method
  262. expect(isAsyncFunction(TestClass.testStaticSync)).toBe(false)
  263. // eslint-disable-next-line @typescript-eslint/unbound-method
  264. expect(isAsyncFunction(TestClass.testStaticAsync)).toBe(true)
  265. })
  266. await it('Verify clone()', () => {
  267. const obj = { 1: 1 }
  268. expect(clone(obj)).toStrictEqual(obj)
  269. expect(clone(obj) === obj).toBe(false)
  270. const nestedObj = { 1: obj, 2: obj }
  271. expect(clone(nestedObj)).toStrictEqual(nestedObj)
  272. expect(clone(nestedObj) === nestedObj).toBe(false)
  273. const array = [1, 2]
  274. expect(clone(array)).toStrictEqual(array)
  275. expect(clone(array) === array).toBe(false)
  276. const objArray = [obj, obj]
  277. expect(clone(objArray)).toStrictEqual(objArray)
  278. expect(clone(objArray) === objArray).toBe(false)
  279. const date = new Date()
  280. expect(clone(date)).toStrictEqual(date)
  281. expect(clone(date) === date).toBe(false)
  282. if (runtime === JSRuntime.node && satisfies(version, '>=22.0.0')) {
  283. const url = new URL('https://domain.tld')
  284. expect(() => clone(url)).toThrow(new Error('Cannot clone object of unsupported type.'))
  285. }
  286. const map = new Map([['1', '2']])
  287. expect(clone(map)).toStrictEqual(map)
  288. expect(clone(map) === map).toBe(false)
  289. const set = new Set(['1'])
  290. expect(clone(set)).toStrictEqual(set)
  291. expect(clone(set) === set).toBe(false)
  292. const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
  293. expect(() => clone(weakMap)).toThrow(new Error('#<WeakMap> could not be cloned.'))
  294. const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
  295. expect(() => clone(weakSet)).toThrow(new Error('#<WeakSet> could not be cloned.'))
  296. })
  297. await it('Verify isNotEmptyString()', () => {
  298. expect(isNotEmptyString('')).toBe(false)
  299. expect(isNotEmptyString(' ')).toBe(false)
  300. expect(isNotEmptyString(' ')).toBe(false)
  301. expect(isNotEmptyString('test')).toBe(true)
  302. expect(isNotEmptyString(' test')).toBe(true)
  303. expect(isNotEmptyString('test ')).toBe(true)
  304. expect(isNotEmptyString(undefined)).toBe(false)
  305. expect(isNotEmptyString(null)).toBe(false)
  306. expect(isNotEmptyString(0)).toBe(false)
  307. expect(isNotEmptyString({})).toBe(false)
  308. expect(isNotEmptyString([])).toBe(false)
  309. expect(isNotEmptyString(new Map())).toBe(false)
  310. expect(isNotEmptyString(new Set())).toBe(false)
  311. expect(isNotEmptyString(new WeakMap())).toBe(false)
  312. expect(isNotEmptyString(new WeakSet())).toBe(false)
  313. })
  314. await it('Verify isNotEmptyArray()', () => {
  315. expect(isNotEmptyArray([])).toBe(false)
  316. expect(isNotEmptyArray([1, 2])).toBe(true)
  317. expect(isNotEmptyArray(['1', '2'])).toBe(true)
  318. expect(isNotEmptyArray(undefined)).toBe(false)
  319. expect(isNotEmptyArray(null)).toBe(false)
  320. expect(isNotEmptyArray('')).toBe(false)
  321. expect(isNotEmptyArray('test')).toBe(false)
  322. expect(isNotEmptyArray(0)).toBe(false)
  323. expect(isNotEmptyArray({})).toBe(false)
  324. expect(isNotEmptyArray(new Map())).toBe(false)
  325. expect(isNotEmptyArray(new Set())).toBe(false)
  326. expect(isNotEmptyArray(new WeakMap())).toBe(false)
  327. expect(isNotEmptyArray(new WeakSet())).toBe(false)
  328. })
  329. await it('Verify insertAt()', () => {
  330. expect(insertAt('test', 'ing', 'test'.length)).toBe('testing')
  331. // eslint-disable-next-line @cspell/spellchecker
  332. expect(insertAt('test', 'ing', 2)).toBe('teingst')
  333. })
  334. await it('Verify isArraySorted()', () => {
  335. expect(isArraySorted<number>([], (a, b) => a - b)).toBe(true)
  336. expect(isArraySorted<number>([1], (a, b) => a - b)).toBe(true)
  337. expect(isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true)
  338. expect(isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
  339. expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
  340. })
  341. })