Logs.utils.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import dayjs from 'dayjs'
  2. import utc from 'dayjs/plugin/utc'
  3. import { describe, expect, test } from 'vitest'
  4. import {
  5. checkForILIKEClause,
  6. checkForWildcard,
  7. checkForWithClause,
  8. fillTimeseries,
  9. } from '@/components/interfaces/Settings/Logs/Logs.utils'
  10. dayjs.extend(utc)
  11. describe('fillTimeseries', () => {
  12. test('should return empty array for empty data without min/max', () => {
  13. const result = fillTimeseries([], 'timestamp', 'value', 0)
  14. expect(result).toEqual([])
  15. })
  16. test('should return empty array for empty data with min/max', () => {
  17. const min = '2023-01-01T00:00:00.000Z'
  18. const max = '2023-01-01T01:00:00.000Z'
  19. const result = fillTimeseries([], 'timestamp', 'value', 0, min, max)
  20. // When min/max are provided, the function fills the time range with default values
  21. // This creates 61 data points (one for each minute from 00:00 to 01:00)
  22. expect(result).toHaveLength(61)
  23. expect(result[0]).toEqual({ timestamp: '2023-01-01T00:00:00.000Z', value: 0 })
  24. expect(result[60]).toEqual({ timestamp: '2023-01-01T01:00:00.000Z', value: 0 })
  25. })
  26. test('should normalize timestamps when data exceeds minPointsToFill', () => {
  27. const data = [
  28. { timestamp: '2023-01-01T00:00:00.000Z', value: 1 },
  29. { timestamp: '2023-01-01T00:01:00.000Z', value: 2 },
  30. { timestamp: '2023-01-01T00:02:00.000Z', value: 3 },
  31. { timestamp: '2023-01-01T00:03:00.000Z', value: 4 },
  32. { timestamp: '2023-01-01T00:04:00.000Z', value: 5 },
  33. { timestamp: '2023-01-01T00:05:00.000Z', value: 6 },
  34. { timestamp: '2023-01-01T00:06:00.000Z', value: 7 },
  35. { timestamp: '2023-01-01T00:07:00.000Z', value: 8 },
  36. { timestamp: '2023-01-01T00:08:00.000Z', value: 9 },
  37. { timestamp: '2023-01-01T00:09:00.000Z', value: 10 },
  38. { timestamp: '2023-01-01T00:10:00.000Z', value: 11 },
  39. { timestamp: '2023-01-01T00:11:00.000Z', value: 12 },
  40. { timestamp: '2023-01-01T00:12:00.000Z', value: 13 },
  41. { timestamp: '2023-01-01T00:13:00.000Z', value: 14 },
  42. { timestamp: '2023-01-01T00:14:00.000Z', value: 15 },
  43. { timestamp: '2023-01-01T00:15:00.000Z', value: 16 },
  44. { timestamp: '2023-01-01T00:16:00.000Z', value: 17 },
  45. { timestamp: '2023-01-01T00:17:00.000Z', value: 18 },
  46. { timestamp: '2023-01-01T00:18:00.000Z', value: 19 },
  47. { timestamp: '2023-01-01T00:19:00.000Z', value: 20 },
  48. { timestamp: '2023-01-01T00:20:00.000Z', value: 21 },
  49. ]
  50. const result = fillTimeseries(data, 'timestamp', 'value', 0, undefined, undefined, 20)
  51. // Should return normalized data without filling gaps
  52. expect(result).toHaveLength(21)
  53. result.forEach((item) => {
  54. expect(item.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
  55. })
  56. })
  57. test('should fill gaps in sparse data with 1-minute intervals', () => {
  58. const min = '2023-01-01T00:00:00.000Z'
  59. const max = '2023-01-01T00:04:00.000Z'
  60. const data = [
  61. { timestamp: '2023-01-01T00:01:00.000Z', value: 10 },
  62. { timestamp: '2023-01-01T00:03:00.000Z', value: 30 },
  63. ]
  64. const result = fillTimeseries(data, 'timestamp', 'value', 0, min, max, 20, '2m')
  65. expect(result).toHaveLength(5)
  66. const sortedResult = result.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
  67. expect(sortedResult[0]).toEqual({ timestamp: '2023-01-01T00:00:00.000Z', value: 0 })
  68. expect(sortedResult[1]).toEqual({ timestamp: '2023-01-01T00:01:00.000Z', value: 10 })
  69. expect(sortedResult[2]).toEqual({ timestamp: '2023-01-01T00:02:00.000Z', value: 0 })
  70. expect(sortedResult[3]).toEqual({ timestamp: '2023-01-01T00:03:00.000Z', value: 30 })
  71. expect(sortedResult[4]).toEqual({ timestamp: '2023-01-01T00:04:00.000Z', value: 0 })
  72. })
  73. test('should handle multiple value keys', () => {
  74. const min = '2023-01-01T00:00:00.000Z'
  75. const max = '2023-01-01T00:02:00.000Z'
  76. const data = [{ timestamp: '2023-01-01T00:01:00.000Z', count1: 10, count2: 100 }]
  77. const result = fillTimeseries(data, 'timestamp', ['count1', 'count2'], 5, min, max, 20, '1m')
  78. expect(result).toHaveLength(3)
  79. const sortedResult = result.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
  80. expect(sortedResult[0]).toEqual({ timestamp: '2023-01-01T00:00:00.000Z', count1: 5, count2: 5 })
  81. expect(sortedResult[1]).toEqual({
  82. timestamp: '2023-01-01T00:01:00.000Z',
  83. count1: 10,
  84. count2: 100,
  85. })
  86. expect(sortedResult[2]).toEqual({ timestamp: '2023-01-01T00:02:00.000Z', count1: 5, count2: 5 })
  87. })
  88. test('should handle different interval formats', () => {
  89. const min = '2023-01-01T00:00:00.000Z'
  90. const max = '2023-01-01T00:10:00.000Z'
  91. const data = [{ timestamp: '2023-01-01T00:05:00.000Z', value: 50 }]
  92. // Test 5-minute intervals: 00:00, 00:05, 00:10 = 3 points
  93. const result5m = fillTimeseries(data, 'timestamp', 'value', 0, min, max, 20, '5m')
  94. expect(result5m).toHaveLength(3)
  95. // Test 2-minute intervals: 00:00, 00:02, 00:04, 00:05, 00:06, 00:08, 00:10 = 7 points
  96. const result2m = fillTimeseries(data, 'timestamp', 'value', 0, min, max, 20, '2m')
  97. console.log(
  98. '2m intervals:',
  99. result2m.map((r) => r.timestamp)
  100. )
  101. expect(result2m).toHaveLength(7)
  102. // Test 1-hour intervals: 00:00, 01:00, 02:00 with existing data at 00:05 = 4 points
  103. const maxHour = '2023-01-01T02:00:00.000Z'
  104. const result1h = fillTimeseries(data, 'timestamp', 'value', 0, min, maxHour, 20, '1h')
  105. expect(result1h).toHaveLength(4)
  106. })
  107. test('should handle microsecond timestamps correctly', () => {
  108. const now = dayjs.utc('2023-01-01T00:00:00.000Z')
  109. const data = [
  110. { timestamp: now.valueOf() * 1000, value: 1 },
  111. { timestamp: now.add(1, 'minute').valueOf() * 1000, value: 2 },
  112. ]
  113. const result = fillTimeseries(data, 'timestamp', 'value', 0, undefined, undefined, 1)
  114. expect(result).toHaveLength(2)
  115. result.forEach((item) => {
  116. expect(item.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
  117. })
  118. })
  119. test('should handle mixed timestamp formats', () => {
  120. const data = [
  121. { timestamp: '2023-01-01T00:00:00.000Z', value: 1 },
  122. { timestamp: dayjs.utc('2023-01-01T00:01:00.000Z').valueOf() * 1000, value: 2 },
  123. { timestamp: '2023-01-01T00:02:00.000Z', value: 3 },
  124. ]
  125. const result = fillTimeseries(data, 'timestamp', 'value', 0, undefined, undefined, 1)
  126. expect(result).toHaveLength(3)
  127. result.forEach((item) => {
  128. expect(item.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
  129. })
  130. })
  131. test('should not fill gaps when data is dense enough', () => {
  132. const data = Array.from({ length: 25 }, (_, i) => ({
  133. timestamp: dayjs.utc('2023-01-01T00:00:00.000Z').add(i, 'minute').toISOString(),
  134. value: i,
  135. }))
  136. const result = fillTimeseries(data, 'timestamp', 'value', 0, undefined, undefined, 20)
  137. expect(result).toHaveLength(25)
  138. expect(result).toEqual(data)
  139. })
  140. test('should handle edge case with single data point', () => {
  141. const data = [{ timestamp: '2023-01-01T00:00:00.000Z', value: 1 }]
  142. const result = fillTimeseries(data, 'timestamp', 'value', 0)
  143. expect(result).toEqual(data)
  144. })
  145. test('should handle edge case with single data point and min/max', () => {
  146. const min = '2023-01-01T00:00:00.000Z'
  147. const max = '2023-01-01T00:02:00.000Z'
  148. const data = [{ timestamp: '2023-01-01T00:01:00.000Z', value: 1 }]
  149. const result = fillTimeseries(data, 'timestamp', 'value', 0, min, max, 20, '1m')
  150. expect(result).toHaveLength(3)
  151. const sortedResult = result.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
  152. expect(sortedResult[0]).toEqual({ timestamp: '2023-01-01T00:00:00.000Z', value: 0 })
  153. expect(sortedResult[1]).toEqual({ timestamp: '2023-01-01T00:01:00.000Z', value: 1 })
  154. expect(sortedResult[2]).toEqual({ timestamp: '2023-01-01T00:02:00.000Z', value: 0 })
  155. })
  156. test('should handle invalid interval format gracefully', () => {
  157. const min = '2023-01-01T00:00:00.000Z'
  158. const max = '2023-01-01T00:02:00.000Z'
  159. const data = [{ timestamp: '2023-01-01T00:01:00.000Z', value: 1 }]
  160. const result = fillTimeseries(data, 'timestamp', 'value', 0, min, max, 20, 'invalid')
  161. // Should fall back to default behavior
  162. expect(result.length).toBeGreaterThan(1)
  163. })
  164. test('should preserve all properties of original data', () => {
  165. const data = [
  166. {
  167. timestamp: '2023-01-01T00:00:00.000Z',
  168. value: 1,
  169. extra: 'data',
  170. nested: { prop: 'value' },
  171. },
  172. ]
  173. const result = fillTimeseries(data, 'timestamp', 'value', 0)
  174. expect(result[0]).toEqual(data[0])
  175. expect(result[0].extra).toBe('data')
  176. expect(result[0].nested).toEqual({ prop: 'value' })
  177. })
  178. test('should handle empty value keys array', () => {
  179. const min = '2023-01-01T00:00:00.000Z'
  180. const max = '2023-01-01T00:01:00.000Z'
  181. const data = [{ timestamp: '2023-01-01T00:00:30.000Z', value: 1 }]
  182. const result = fillTimeseries(data, 'timestamp', [], 0, min, max, 20, '30s')
  183. expect(result).toHaveLength(3)
  184. result.forEach((item) => {
  185. expect(item).toHaveProperty('timestamp')
  186. expect(item).not.toHaveProperty('value')
  187. })
  188. })
  189. })
  190. describe('checkForWithClause', () => {
  191. test('basic queries', () => {
  192. expect(checkForWithClause('SELECT * FROM table')).toBe(false)
  193. expect(checkForWithClause('SELECT * FROM table WITH clause')).toBe(true)
  194. expect(checkForWithClause('WITH test AS (SELECT * FROM table) SELECT * FROM test')).toBe(true)
  195. expect(checkForWithClause('SELECT * FROM withsomething')).toBe(false)
  196. })
  197. test('case sensitivity', () => {
  198. expect(checkForWithClause('with test AS (SELECT * FROM table) SELECT * FROM test')).toBe(true)
  199. expect(checkForWithClause('WiTh test AS (SELECT * FROM table) SELECT * FROM test')).toBe(true)
  200. })
  201. test('comments', () => {
  202. expect(checkForWithClause('SELECT * FROM table -- WITH clause')).toBe(false)
  203. expect(checkForWithClause('SELECT * FROM table /* WITH clause */')).toBe(false)
  204. expect(checkForWithClause('-- WITH clause\nSELECT * FROM table')).toBe(false)
  205. expect(checkForWithClause('/* WITH clause */\nSELECT * FROM table')).toBe(false)
  206. })
  207. test('string literals', () => {
  208. expect(checkForWithClause("SELECT 'WITH' FROM table")).toBe(false)
  209. expect(checkForWithClause("SELECT * FROM table WHERE column = 'WITH clause'")).toBe(false)
  210. })
  211. test('subqueries', () => {
  212. expect(
  213. checkForWithClause('SELECT * FROM (WITH subquery AS (SELECT 1) SELECT * FROM subquery)')
  214. ).toBe(true)
  215. expect(
  216. checkForWithClause(
  217. 'SELECT * FROM table WHERE column IN (WITH subquery AS (SELECT 1) SELECT * FROM subquery)'
  218. )
  219. ).toBe(true)
  220. })
  221. })
  222. describe('checkForILIKEClause', () => {
  223. test('basic queries', () => {
  224. expect(checkForILIKEClause('SELECT * FROM table')).toBe(false)
  225. expect(checkForILIKEClause('SELECT * FROM table WHERE column ILIKE "%value%"')).toBe(true)
  226. expect(checkForILIKEClause('SELECT * FROM table WHERE column LIKE "%value%"')).toBe(false)
  227. expect(checkForILIKEClause('SELECT * FROM ilikesomething')).toBe(false)
  228. })
  229. test('case sensitivity', () => {
  230. expect(checkForILIKEClause('SELECT * FROM table WHERE column ilike "%value%"')).toBe(true)
  231. expect(checkForILIKEClause('SELECT * FROM table WHERE column IlIkE "%value%"')).toBe(true)
  232. })
  233. test('comments', () => {
  234. expect(checkForILIKEClause('SELECT * FROM table -- ILIKE clause')).toBe(false)
  235. expect(checkForILIKEClause('SELECT * FROM table /* ILIKE clause */')).toBe(false)
  236. expect(checkForILIKEClause('-- ILIKE clause\nSELECT * FROM table')).toBe(false)
  237. expect(checkForILIKEClause('/* ILIKE clause */\nSELECT * FROM table')).toBe(false)
  238. })
  239. test('string literals', () => {
  240. expect(checkForILIKEClause("SELECT 'ILIKE' FROM table")).toBe(false)
  241. expect(checkForILIKEClause("SELECT * FROM table WHERE column = 'ILIKE clause'")).toBe(false)
  242. })
  243. test('subqueries', () => {
  244. expect(
  245. checkForILIKEClause('SELECT * FROM (SELECT * FROM table WHERE column ILIKE "%value%")')
  246. ).toBe(true)
  247. expect(
  248. checkForILIKEClause(
  249. 'SELECT * FROM table WHERE column IN (SELECT * FROM subtable WHERE column ILIKE "%value%")'
  250. )
  251. ).toBe(true)
  252. })
  253. })
  254. describe('checkForWildcard', () => {
  255. test('basic queries', () => {
  256. expect(checkForWildcard('SELECT * FROM table')).toBe(true)
  257. expect(checkForWildcard('SELECT column FROM table')).toBe(false)
  258. })
  259. test('comments', () => {
  260. expect(checkForWildcard('SELECT column FROM table -- *')).toBe(false)
  261. expect(checkForWildcard('SELECT column FROM table /* * */')).toBe(false)
  262. expect(checkForWildcard('-- *\nSELECT column FROM table')).toBe(false)
  263. expect(checkForWildcard('/* * */\nSELECT column FROM table')).toBe(false)
  264. })
  265. test('count(*)', () => {
  266. expect(checkForWildcard('SELECT count(*) FROM table')).toBe(false)
  267. })
  268. })