ChartDataTransform.utils.test.ts221 lines · main
1import dayjs from 'dayjs'
2import { describe, expect, it } from 'vitest'
3
4import { normalizeChartBuckets } from './ChartDataTransform.utils'
5import type { LogsBarChartDatum } from './ProjectUsage.metrics'
6
7describe('normalizeChartBuckets', () => {
8 const now = dayjs('2024-01-28T12:00:00.000Z')
9
10 describe('1hr interval', () => {
11 it('should create exactly 30 buckets with 2-minute intervals', () => {
12 const result = normalizeChartBuckets([], '1hr', now.toDate())
13
14 expect(result).toHaveLength(30)
15
16 // Check first bucket
17 expect(result[0].timestamp).toBe(now.subtract(60, 'minute').toISOString())
18
19 // Check last bucket
20 expect(result[29].timestamp).toBe(now.subtract(2, 'minute').toISOString())
21
22 // Check all buckets are 2 minutes apart
23 for (let i = 0; i < result.length - 1; i++) {
24 const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'minute')
25 expect(diff).toBe(2)
26 }
27 })
28
29 it('should aggregate data points into correct 2-minute buckets', () => {
30 const data: LogsBarChartDatum[] = [
31 {
32 // First bucket starts at -60 minutes, so -60 to -59 minutes is in bucket 0
33 timestamp: now.subtract(60, 'minute').toISOString(),
34 ok_count: 10,
35 warning_count: 1,
36 error_count: 2,
37 },
38 {
39 timestamp: now.subtract(59, 'minute').add(30, 'second').toISOString(),
40 ok_count: 5,
41 warning_count: 0,
42 error_count: 1,
43 },
44 {
45 timestamp: now.subtract(30, 'minute').toISOString(),
46 ok_count: 20,
47 warning_count: 2,
48 error_count: 0,
49 },
50 ]
51
52 const result = normalizeChartBuckets(data, '1hr', now.toDate())
53
54 // First bucket (60-58 minutes ago) should contain aggregated data
55 const firstBucket = result[0]
56 expect(firstBucket.ok_count).toBe(15) // 10 + 5
57 expect(firstBucket.warning_count).toBe(1) // 1 + 0
58 expect(firstBucket.error_count).toBe(3) // 2 + 1
59
60 // Bucket at 30 minutes ago
61 const bucket15 = result[15] // 30 minutes / 2 minutes per bucket = bucket 15
62 expect(bucket15.ok_count).toBe(20)
63 expect(bucket15.warning_count).toBe(2)
64 expect(bucket15.error_count).toBe(0)
65 })
66
67 it('should return empty buckets when no data provided', () => {
68 const result = normalizeChartBuckets([], '1hr', now.toDate())
69
70 expect(result).toHaveLength(30)
71 result.forEach((bucket) => {
72 expect(bucket.ok_count).toBe(0)
73 expect(bucket.warning_count).toBe(0)
74 expect(bucket.error_count).toBe(0)
75 })
76 })
77 })
78
79 describe('1day interval', () => {
80 it('should create exactly 24 buckets with 1-hour intervals', () => {
81 const result = normalizeChartBuckets([], '1day', now.toDate())
82
83 expect(result).toHaveLength(24)
84
85 // Check first bucket
86 expect(result[0].timestamp).toBe(now.subtract(24, 'hour').toISOString())
87
88 // Check last bucket
89 expect(result[23].timestamp).toBe(now.subtract(1, 'hour').toISOString())
90
91 // Check all buckets are 1 hour apart
92 for (let i = 0; i < result.length - 1; i++) {
93 const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
94 expect(diff).toBe(1)
95 }
96 })
97
98 it('should aggregate multiple data points into hourly buckets', () => {
99 const data: LogsBarChartDatum[] = [
100 {
101 timestamp: now.subtract(23, 'hour').subtract(30, 'minute').toISOString(),
102 ok_count: 100,
103 warning_count: 5,
104 error_count: 3,
105 },
106 {
107 timestamp: now.subtract(23, 'hour').subtract(15, 'minute').toISOString(),
108 ok_count: 50,
109 warning_count: 2,
110 error_count: 1,
111 },
112 ]
113
114 const result = normalizeChartBuckets(data, '1day', now.toDate())
115
116 // First bucket should contain aggregated data
117 expect(result[0].ok_count).toBe(150)
118 expect(result[0].warning_count).toBe(7)
119 expect(result[0].error_count).toBe(4)
120 })
121 })
122
123 describe('7day interval', () => {
124 it('should create exactly 28 buckets with 6-hour intervals', () => {
125 const result = normalizeChartBuckets([], '7day', now.toDate())
126
127 expect(result).toHaveLength(28)
128
129 // Check first bucket (7 days = 168 hours ago)
130 expect(result[0].timestamp).toBe(now.subtract(168, 'hour').toISOString())
131
132 // Check last bucket
133 expect(result[27].timestamp).toBe(now.subtract(6, 'hour').toISOString())
134
135 // Check all buckets are 6 hours apart
136 for (let i = 0; i < result.length - 1; i++) {
137 const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
138 expect(diff).toBe(6)
139 }
140 })
141
142 it('should aggregate data points into 6-hour buckets', () => {
143 const data: LogsBarChartDatum[] = [
144 {
145 timestamp: now.subtract(167, 'hour').toISOString(),
146 ok_count: 1000,
147 warning_count: 10,
148 error_count: 5,
149 },
150 {
151 timestamp: now.subtract(165, 'hour').toISOString(),
152 ok_count: 500,
153 warning_count: 5,
154 error_count: 2,
155 },
156 ]
157
158 const result = normalizeChartBuckets(data, '7day', now.toDate())
159
160 // First bucket should contain aggregated data
161 expect(result[0].ok_count).toBe(1500)
162 expect(result[0].warning_count).toBe(15)
163 expect(result[0].error_count).toBe(7)
164 })
165 })
166
167 describe('edge cases', () => {
168 it('should handle data points outside the time range', () => {
169 const data: LogsBarChartDatum[] = [
170 {
171 timestamp: now.subtract(120, 'minute').toISOString(), // Outside 1hr range
172 ok_count: 100,
173 warning_count: 10,
174 error_count: 5,
175 },
176 {
177 timestamp: now.add(10, 'minute').toISOString(), // Future data
178 ok_count: 50,
179 warning_count: 5,
180 error_count: 2,
181 },
182 {
183 timestamp: now.subtract(30, 'minute').toISOString(), // Within range
184 ok_count: 25,
185 warning_count: 2,
186 error_count: 1,
187 },
188 ]
189
190 const result = normalizeChartBuckets(data, '1hr', now.toDate())
191
192 // Should only include the data within range
193 const validBucket = result[15] // 30 minutes ago
194 expect(validBucket.ok_count).toBe(25)
195 expect(validBucket.warning_count).toBe(2)
196 expect(validBucket.error_count).toBe(1)
197
198 // Other buckets should be empty
199 expect(result[0].ok_count).toBe(0)
200 expect(result[29].ok_count).toBe(0)
201 })
202
203 it('should handle undefined/null values in data', () => {
204 const data: LogsBarChartDatum[] = [
205 {
206 timestamp: now.subtract(30, 'minute').toISOString(),
207 ok_count: undefined as any,
208 warning_count: null as any,
209 error_count: 5,
210 },
211 ]
212
213 const result = normalizeChartBuckets(data, '1hr', now.toDate())
214
215 const bucket = result[15]
216 expect(bucket.ok_count).toBe(0)
217 expect(bucket.warning_count).toBe(0)
218 expect(bucket.error_count).toBe(5)
219 })
220 })
221})