@@ -3,7 +3,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
33
44import { cache } from 'storybook/internal/common' ;
55
6- import { get , getLastEvents , getPrecedingUpgrade , set } from './event-cache' ;
6+ import type { CacheEntry } from './event-cache' ;
7+ import { getLastEvents , getPrecedingUpgrade , set } from './event-cache' ;
8+ import type { TelemetryEvent } from './types' ;
79
810vi . mock ( 'storybook/internal/common' , { spy : true } ) ;
911
@@ -12,54 +14,94 @@ expect.addSnapshotSerializer({
1214 test : ( val ) => typeof val !== 'string' ,
1315} ) ;
1416
17+ // Helper to create valid TelemetryEvent objects
18+ const createTelemetryEvent = (
19+ eventType : TelemetryEvent [ 'eventType' ] ,
20+ eventId : string ,
21+ overrides ?: Partial < TelemetryEvent >
22+ ) : TelemetryEvent => ( {
23+ eventType,
24+ eventId,
25+ sessionId : 'test-session' ,
26+ context : { } ,
27+ payload : { } ,
28+ ...overrides ,
29+ } ) ;
30+
1531describe ( 'event-cache' , ( ) => {
16- const init = { body : { eventType : 'init' , eventId : 'init' } , timestamp : 1 } ;
17- const upgrade = { body : { eventType : 'upgrade' , eventId : 'upgrade' } , timestamp : 2 } ;
18- const dev = { body : { eventType : 'dev' , eventId : 'dev' } , timestamp : 3 } ;
19- const build = { body : { eventType : 'build' , eventId : 'build' } , timestamp : 3 } ;
20- const error = { body : { eventType : 'build' , eventId : 'error' } , timestamp : 4 } ;
21- const versionUpdate = {
22- body : { eventType : 'version-update' , eventId : 'version-update' } ,
32+ const init : CacheEntry = {
33+ body : createTelemetryEvent ( 'init' , 'init' ) ,
34+ timestamp : 1 ,
35+ } ;
36+ const upgrade : CacheEntry = {
37+ body : createTelemetryEvent ( 'upgrade' , 'upgrade' ) ,
38+ timestamp : 2 ,
39+ } ;
40+ const dev : CacheEntry = {
41+ body : createTelemetryEvent ( 'dev' , 'dev' ) ,
42+ timestamp : 3 ,
43+ } ;
44+ const build : CacheEntry = {
45+ body : createTelemetryEvent ( 'build' , 'build' ) ,
46+ timestamp : 3 ,
47+ } ;
48+ const error : CacheEntry = {
49+ body : createTelemetryEvent ( 'build' , 'error' ) ,
50+ timestamp : 4 ,
51+ } ;
52+ const versionUpdate : CacheEntry = {
53+ body : createTelemetryEvent ( 'version-update' , 'version-update' ) ,
2354 timestamp : 5 ,
2455 } ;
2556
2657 describe ( 'data handling' , ( ) => {
2758 it ( 'errors' , async ( ) => {
2859 const preceding = await getPrecedingUpgrade ( {
29- init : { timestamp : 1 , body : { ...init . body , error : { } } } ,
60+ init : {
61+ timestamp : 1 ,
62+ body : { ...init . body , error : { } } as TelemetryEvent & { error : unknown } ,
63+ } ,
3064 } ) ;
3165 expect ( preceding ) . toMatchInlineSnapshot ( `
3266 {
3367 "timestamp": 1,
3468 "eventType": "init",
35- "eventId": "init"
69+ "eventId": "init",
70+ "sessionId": "test-session"
3671 }
3772 ` ) ;
3873 } ) ;
3974
4075 it ( 'session IDs' , async ( ) => {
4176 const preceding = await getPrecedingUpgrade ( {
42- init : { timestamp : 1 , body : { ...init . body , sessionId : 100 } } ,
77+ init : {
78+ timestamp : 1 ,
79+ body : { ...init . body , sessionId : '100' } ,
80+ } ,
4381 } ) ;
4482 expect ( preceding ) . toMatchInlineSnapshot ( `
4583 {
4684 "timestamp": 1,
4785 "eventType": "init",
4886 "eventId": "init",
49- "sessionId": 100
87+ "sessionId": " 100"
5088 }
5189 ` ) ;
5290 } ) ;
5391
5492 it ( 'extra fields' , async ( ) => {
5593 const preceding = await getPrecedingUpgrade ( {
56- init : { timestamp : 1 , body : { ...init . body , foobar : 'baz' } } ,
94+ init : {
95+ timestamp : 1 ,
96+ body : { ...init . body , foobar : 'baz' } as TelemetryEvent & { foobar : string } ,
97+ } ,
5798 } ) ;
5899 expect ( preceding ) . toMatchInlineSnapshot ( `
59100 {
60101 "timestamp": 1,
61102 "eventType": "init",
62- "eventId": "init"
103+ "eventId": "init",
104+ "sessionId": "test-session"
63105 }
64106 ` ) ;
65107 } ) ;
@@ -77,7 +119,8 @@ describe('event-cache', () => {
77119 {
78120 "timestamp": 1,
79121 "eventType": "init",
80- "eventId": "init"
122+ "eventId": "init",
123+ "sessionId": "test-session"
81124 }
82125 ` ) ;
83126 } ) ;
@@ -88,7 +131,8 @@ describe('event-cache', () => {
88131 {
89132 "timestamp": 2,
90133 "eventType": "upgrade",
91- "eventId": "upgrade"
134+ "eventId": "upgrade",
135+ "sessionId": "test-session"
92136 }
93137 ` ) ;
94138 } ) ;
@@ -99,7 +143,8 @@ describe('event-cache', () => {
99143 {
100144 "timestamp": 2,
101145 "eventType": "upgrade",
102- "eventId": "upgrade"
146+ "eventId": "upgrade",
147+ "sessionId": "test-session"
103148 }
104149 ` ) ;
105150 } ) ;
@@ -127,31 +172,33 @@ describe('event-cache', () => {
127172 } ) ;
128173
129174 it ( 'both init and upgrade with intervening dev' , async ( ) => {
130- const secondUpgrade = {
131- body : { eventType : 'upgrade' , eventId : 'secondUpgrade' } ,
175+ const secondUpgrade : CacheEntry = {
176+ body : createTelemetryEvent ( 'upgrade' , 'secondUpgrade' ) ,
132177 timestamp : 4 ,
133178 } ;
134179 const preceding = await getPrecedingUpgrade ( { init, dev, upgrade : secondUpgrade } ) ;
135180 expect ( preceding ) . toMatchInlineSnapshot ( `
136181 {
137182 "timestamp": 4,
138183 "eventType": "upgrade",
139- "eventId": "secondUpgrade"
184+ "eventId": "secondUpgrade",
185+ "sessionId": "test-session"
140186 }
141187 ` ) ;
142188 } ) ;
143189
144190 it ( 'both init and upgrade with non-intervening dev' , async ( ) => {
145- const earlyDev = {
146- body : { eventType : 'dev' , eventId : 'earlyDev' } ,
191+ const earlyDev : CacheEntry = {
192+ body : createTelemetryEvent ( 'dev' , 'earlyDev' ) ,
147193 timestamp : - 1 ,
148194 } ;
149195 const preceding = await getPrecedingUpgrade ( { dev : earlyDev , init, upgrade } ) ;
150196 expect ( preceding ) . toMatchInlineSnapshot ( `
151197 {
152198 "timestamp": 2,
153199 "eventType": "upgrade",
154- "eventId": "upgrade"
200+ "eventId": "upgrade",
201+ "sessionId": "test-session"
155202 }
156203 ` ) ;
157204 } ) ;
@@ -174,7 +221,8 @@ describe('event-cache', () => {
174221 {
175222 "timestamp": 2,
176223 "eventType": "upgrade",
177- "eventId": "upgrade"
224+ "eventId": "upgrade",
225+ "sessionId": "test-session"
178226 }
179227 ` ) ;
180228 } ) ;
@@ -192,46 +240,28 @@ describe('event-cache', () => {
192240
193241 it ( 'getLastEvents waits for pending set operations to complete' , async ( ) => {
194242 const initialData = {
195- init : { timestamp : 1 , body : { eventType : 'init' , eventId : 'init-1' } } ,
243+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
196244 } ;
197245 const updatedData = {
198- init : { timestamp : 1 , body : { eventType : 'init' , eventId : 'init-1' } } ,
199- upgrade : { timestamp : 2 , body : { eventType : 'upgrade' , eventId : 'upgrade-1' } } ,
246+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
247+ upgrade : { timestamp : 2 , body : createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) } ,
200248 } ;
201249
202- // Use a simple delay to simulate async operations
203- let setGetResolved = false ;
204- let setSetResolved = false ;
250+ // Mock cache.get to return initial data first, then updated data
251+ cacheGetMock
252+ . mockResolvedValueOnce ( initialData ) // First call in setHelper
253+ . mockResolvedValueOnce ( updatedData ) ; // Second call in getLastEvents
205254
206- cacheGetMock . mockImplementationOnce ( async ( ) => {
207- while ( ! setGetResolved ) {
208- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
209- }
210- return initialData ;
211- } ) ;
212-
213- cacheSetMock . mockImplementationOnce ( async ( ) => {
214- while ( ! setSetResolved ) {
215- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
216- }
217- } ) ;
255+ // Mock cache.set to resolve immediately
256+ cacheSetMock . mockResolvedValue ( undefined ) ;
218257
219- // Mock cache.get to return updated data after set completes
220- cacheGetMock . mockResolvedValueOnce ( updatedData ) ;
221-
222- // Start a set operation (this will be pending)
223- const setPromiseResult = set ( 'upgrade' , { eventType : 'upgrade' , eventId : 'upgrade-1' } ) ;
258+ // Start a set operation (this will be queued and processed)
259+ const setPromiseResult = set ( 'upgrade' , createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) ) ;
224260
225261 // Immediately call getLastEvents() - it should wait for set() to complete
226262 const getPromise = getLastEvents ( ) ;
227263
228- // Verify that getLastEvents hasn't resolved yet (it's waiting)
229- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
230-
231- // Resolve the set operations
232- setGetResolved = true ;
233- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
234- setSetResolved = true ;
264+ // Wait for set operation to complete
235265 await setPromiseResult ;
236266
237267 // Now getLastEvents should complete and return the updated data
@@ -242,5 +272,79 @@ describe('event-cache', () => {
242272 expect ( cacheGetMock ) . toHaveBeenCalledTimes ( 2 ) ; // Once in setHelper, once in getLastEvents
243273 expect ( cacheSetMock ) . toHaveBeenCalledTimes ( 1 ) ;
244274 } ) ;
275+
276+ it ( 'queues multiple set operations sequentially' , async ( ) => {
277+ const initialData = { } ;
278+ const afterFirst = {
279+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
280+ } ;
281+ const afterSecond = {
282+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
283+ upgrade : { timestamp : 2 , body : createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) } ,
284+ } ;
285+ const afterThird = {
286+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
287+ upgrade : { timestamp : 2 , body : createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) } ,
288+ dev : { timestamp : 3 , body : createTelemetryEvent ( 'dev' , 'dev-1' ) } ,
289+ } ;
290+
291+ // Mock cache.get to return data in sequence
292+ cacheGetMock
293+ . mockResolvedValueOnce ( initialData ) // First set: get initial
294+ . mockResolvedValueOnce ( afterFirst ) // Second set: get after first
295+ . mockResolvedValueOnce ( afterSecond ) // Third set: get after second
296+ . mockResolvedValueOnce ( afterThird ) ; // getLastEvents: get after third
297+
298+ // Mock cache.set to resolve immediately
299+ cacheSetMock . mockResolvedValue ( undefined ) ;
300+
301+ // Queue multiple set operations
302+ const set1 = set ( 'init' , createTelemetryEvent ( 'init' , 'init-1' ) ) ;
303+ const set2 = set ( 'upgrade' , createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) ) ;
304+ const set3 = set ( 'dev' , createTelemetryEvent ( 'dev' , 'dev-1' ) ) ;
305+
306+ // Wait for all operations to complete
307+ await Promise . all ( [ set1 , set2 , set3 ] ) ;
308+
309+ // Now getLastEvents should return the final state
310+ const result = await getLastEvents ( ) ;
311+
312+ // Verify all operations were processed sequentially
313+ expect ( result ) . toEqual ( afterThird ) ;
314+ expect ( cacheGetMock ) . toHaveBeenCalledTimes ( 4 ) ; // 3 sets + 1 getLastEvents
315+ expect ( cacheSetMock ) . toHaveBeenCalledTimes ( 3 ) ; // One for each set
316+ } ) ;
317+
318+ it ( 'handles errors in queued operations' , async ( ) => {
319+ const initialData = {
320+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
321+ } ;
322+ const afterDev = {
323+ init : { timestamp : 1 , body : createTelemetryEvent ( 'init' , 'init-1' ) } ,
324+ dev : { timestamp : 3 , body : createTelemetryEvent ( 'dev' , 'dev-1' ) } ,
325+ } ;
326+
327+ // First operation will fail
328+ cacheGetMock . mockResolvedValueOnce ( initialData ) ;
329+ cacheSetMock . mockRejectedValueOnce ( new Error ( 'Cache write failed' ) ) ;
330+
331+ // Queue an operation that will fail
332+ const failedOperation = set ( 'upgrade' , createTelemetryEvent ( 'upgrade' , 'upgrade-1' ) ) ;
333+ await expect ( failedOperation ) . rejects . toThrow ( 'Cache write failed' ) ;
334+
335+ // Wait a bit to ensure queue processing completes
336+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
337+
338+ // Verify subsequent operations can still be queued and succeed
339+ cacheGetMock . mockResolvedValueOnce ( initialData ) ;
340+ cacheSetMock . mockResolvedValueOnce ( undefined ) ;
341+ cacheGetMock . mockResolvedValueOnce ( afterDev ) ;
342+
343+ await expect ( set ( 'dev' , createTelemetryEvent ( 'dev' , 'dev-1' ) ) ) . resolves . toBeUndefined ( ) ;
344+
345+ // Verify the successful operation was processed
346+ const result = await getLastEvents ( ) ;
347+ expect ( result ) . toEqual ( afterDev ) ;
348+ } ) ;
245349 } ) ;
246350} ) ;
0 commit comments