11import { describe , expect , it } from 'vitest'
22import { applyBlocklistCorrection } from '../../../../app/utils/download-anomalies'
3- import type { WeeklyDataPoint } from '../../../../app/types/chart'
3+ import type {
4+ MonthlyDataPoint ,
5+ WeeklyDataPoint ,
6+ YearlyDataPoint ,
7+ } from '../../../../app/types/chart'
8+
9+ /** Helper to build a WeeklyDataPoint from a start date and value. */
10+ function week ( weekStart : string , value : number ) : WeeklyDataPoint {
11+ const start = new Date ( `${ weekStart } T00:00:00Z` )
12+ const end = new Date ( start )
13+ end . setUTCDate ( end . getUTCDate ( ) + 6 )
14+ const weekEnd = end . toISOString ( ) . slice ( 0 , 10 )
15+ return {
16+ value,
17+ weekKey : `${ weekStart } _${ weekEnd } ` ,
18+ weekStart,
19+ weekEnd,
20+ timestampStart : start . getTime ( ) ,
21+ timestampEnd : end . getTime ( ) ,
22+ }
23+ }
24+
25+ function month ( monthStr : string , value : number ) : MonthlyDataPoint {
26+ return {
27+ value,
28+ month : monthStr ,
29+ timestamp : new Date ( `${ monthStr } -01T00:00:00Z` ) . getTime ( ) ,
30+ }
31+ }
32+
33+ function year ( yearStr : string , value : number ) : YearlyDataPoint {
34+ return {
35+ value,
36+ year : yearStr ,
37+ timestamp : new Date ( `${ yearStr } -01-01T00:00:00Z` ) . getTime ( ) ,
38+ }
39+ }
440
541describe ( 'applyBlocklistCorrection' , ( ) => {
6- it ( 'treats overlapping weekly buckets as affected anomalies' , ( ) => {
7- const data : WeeklyDataPoint [ ] = [
8- {
9- value : 100 ,
10- weekKey : '2022-11-07_2022-11-13' ,
11- weekStart : '2022-11-07' ,
12- weekEnd : '2022-11-13' ,
13- timestampStart : 0 ,
14- timestampEnd : 0 ,
15- } ,
16- {
17- value : 999 ,
18- weekKey : '2022-11-14_2022-11-20' ,
19- weekStart : '2022-11-14' ,
20- weekEnd : '2022-11-20' ,
21- timestampStart : 0 ,
22- timestampEnd : 0 ,
23- } ,
24- {
25- value : 999 ,
26- weekKey : '2022-11-21_2022-11-27' ,
27- weekStart : '2022-11-21' ,
28- weekEnd : '2022-11-27' ,
29- timestampStart : 0 ,
30- timestampEnd : 0 ,
31- } ,
32- {
33- value : 999 ,
34- weekKey : '2022-11-28_2022-12-04' ,
35- weekStart : '2022-11-28' ,
36- weekEnd : '2022-12-04' ,
37- timestampStart : 0 ,
38- timestampEnd : 0 ,
39- } ,
40- {
41- value : 200 ,
42- weekKey : '2022-12-05_2022-12-11' ,
43- weekStart : '2022-12-05' ,
44- weekEnd : '2022-12-11' ,
45- timestampStart : 0 ,
46- timestampEnd : 0 ,
47- } ,
42+ // Anomaly Nov 2022: start=2022-11-15, end=2022-11-30
43+ it ( 'corrects weeks overlapping the anomaly' , ( ) => {
44+ const data = [
45+ week ( '2022-11-07' , 100 ) ,
46+ week ( '2022-11-14' , 999 ) ,
47+ week ( '2022-11-21' , 999 ) ,
48+ week ( '2022-11-28' , 999 ) ,
49+ week ( '2022-12-05' , 200 ) ,
4850 ]
4951
5052 expect (
@@ -61,4 +63,121 @@ describe('applyBlocklistCorrection', () => {
6163 data [ 4 ] ,
6264 ] )
6365 } )
66+
67+ // Anomaly Jun 2023: start=2023-06-19, end=2023-06-22
68+ it ( 'does not over-correct a week starting on the anomaly end boundary' , ( ) => {
69+ const data = [
70+ week ( '2023-06-01' , 500_000 ) ,
71+ week ( '2023-06-08' , 500_000 ) ,
72+ week ( '2023-06-15' , 10_000_000 ) , // contains spike
73+ week ( '2023-06-22' , 500_000 ) , // starts on anomaly end boundary — normal!
74+ week ( '2023-06-29' , 500_000 ) ,
75+ ]
76+
77+ const result = applyBlocklistCorrection ( {
78+ data,
79+ packageName : 'svelte' ,
80+ granularity : 'weekly' ,
81+ } ) as WeeklyDataPoint [ ]
82+
83+ // The spike week must be corrected
84+ expect ( result [ 2 ] ! . hasAnomaly ) . toBe ( true )
85+ expect ( result [ 2 ] ! . value ) . toBeLessThan ( 1_000_000 )
86+
87+ // The boundary week must NOT be modified
88+ expect ( result [ 3 ] ! . value ) . toBe ( 500_000 )
89+ expect ( result [ 3 ] ! . hasAnomaly ) . toBeUndefined ( )
90+ } )
91+
92+ it ( 'does not over-correct a week ending on the anomaly start boundary' , ( ) => {
93+ const data = [
94+ week ( '2023-06-13' , 500_000 ) , // ends on anomaly start boundary — normal!
95+ week ( '2023-06-20' , 10_000_000 ) , // contains spike
96+ week ( '2023-06-27' , 500_000 ) ,
97+ ]
98+
99+ const result = applyBlocklistCorrection ( {
100+ data,
101+ packageName : 'svelte' ,
102+ granularity : 'weekly' ,
103+ } ) as WeeklyDataPoint [ ]
104+
105+ // The boundary week must NOT be modified
106+ expect ( result [ 0 ] ! . value ) . toBe ( 500_000 )
107+ expect ( result [ 0 ] ! . hasAnomaly ) . toBeUndefined ( )
108+
109+ // The spike week must be corrected
110+ expect ( result [ 1 ] ! . hasAnomaly ) . toBe ( true )
111+ expect ( result [ 1 ] ! . value ) . toBeLessThan ( 1_000_000 )
112+ } )
113+
114+ // Vite anomaly: start=2025-08-04, end=2025-09-08 (spans Aug-Sep)
115+ it ( 'does not over-correct a month that only touches the anomaly end boundary' , ( ) => {
116+ const data = [
117+ month ( '2025-07' , 30_000_000 ) ,
118+ month ( '2025-08' , 100_000_000 ) , // contains spike
119+ month ( '2025-09' , 100_000_000 ) , // contains spike (Sep 1-7)
120+ month ( '2025-10' , 30_000_000 ) , // after anomaly end — normal!
121+ ]
122+
123+ const result = applyBlocklistCorrection ( {
124+ data,
125+ packageName : 'vite' ,
126+ granularity : 'monthly' ,
127+ } ) as MonthlyDataPoint [ ]
128+
129+ expect ( result [ 1 ] ! . hasAnomaly ) . toBe ( true )
130+ expect ( result [ 2 ] ! . hasAnomaly ) . toBe ( true )
131+
132+ // October must NOT be modified
133+ expect ( result [ 3 ] ! . value ) . toBe ( 30_000_000 )
134+ expect ( result [ 3 ] ! . hasAnomaly ) . toBeUndefined ( )
135+ } )
136+
137+ it ( 'does not over-correct a month that only touches the anomaly start boundary' , ( ) => {
138+ const data = [
139+ month ( '2025-07' , 30_000_000 ) , // before anomaly start — normal!
140+ month ( '2025-08' , 100_000_000 ) , // contains spike
141+ month ( '2025-09' , 100_000_000 ) , // contains spike
142+ month ( '2025-10' , 30_000_000 ) ,
143+ ]
144+
145+ const result = applyBlocklistCorrection ( {
146+ data,
147+ packageName : 'vite' ,
148+ granularity : 'monthly' ,
149+ } ) as MonthlyDataPoint [ ]
150+
151+ // July must NOT be modified
152+ expect ( result [ 0 ] ! . value ) . toBe ( 30_000_000 )
153+ expect ( result [ 0 ] ! . hasAnomaly ) . toBeUndefined ( )
154+
155+ expect ( result [ 1 ] ! . hasAnomaly ) . toBe ( true )
156+ expect ( result [ 2 ] ! . hasAnomaly ) . toBe ( true )
157+ } )
158+
159+ it ( 'does not over-correct a year that only touches the anomaly boundary' , ( ) => {
160+ const data = [
161+ year ( '2024' , 500_000_000 ) ,
162+ year ( '2025' , 2_000_000_000 ) , // contains spike
163+ year ( '2026' , 500_000_000 ) ,
164+ ]
165+
166+ const result = applyBlocklistCorrection ( {
167+ data,
168+ packageName : 'vite' ,
169+ granularity : 'yearly' ,
170+ } ) as YearlyDataPoint [ ]
171+
172+ // 2024 must NOT be modified
173+ expect ( result [ 0 ] ! . value ) . toBe ( 500_000_000 )
174+ expect ( result [ 0 ] ! . hasAnomaly ) . toBeUndefined ( )
175+
176+ // 2025 must be corrected
177+ expect ( result [ 1 ] ! . hasAnomaly ) . toBe ( true )
178+
179+ // 2026 must NOT be modified
180+ expect ( result [ 2 ] ! . value ) . toBe ( 500_000_000 )
181+ expect ( result [ 2 ] ! . hasAnomaly ) . toBeUndefined ( )
182+ } )
64183} )
0 commit comments