1
1
import { ClickhouseClient } from '../clickhouse' ;
2
- import { Metadata } from '../metadata' ;
2
+ import { Metadata , MetadataCache } from '../metadata' ;
3
3
import * as renderChartConfigModule from '../renderChartConfig' ;
4
4
import { ChartConfigWithDateRange } from '../types' ;
5
5
@@ -20,6 +20,109 @@ jest.mock('../renderChartConfig', () => ({
20
20
. mockResolvedValue ( { sql : 'SELECT 1' , params : { } } ) ,
21
21
} ) ) ;
22
22
23
+ describe ( 'MetadataCache' , ( ) => {
24
+ let metadataCache : MetadataCache ;
25
+
26
+ beforeEach ( ( ) => {
27
+ metadataCache = new MetadataCache ( ) ;
28
+ jest . clearAllMocks ( ) ;
29
+ } ) ;
30
+
31
+ describe ( 'getOrFetch' , ( ) => {
32
+ it ( 'should return cached value if it exists' , async ( ) => {
33
+ const key = 'test-key' ;
34
+ const value = { data : 'test-data' } ;
35
+
36
+ // Set a value in the cache
37
+ metadataCache . set ( key , value ) ;
38
+
39
+ // Mock query function that should not be called
40
+ const queryFn = jest . fn ( ) . mockResolvedValue ( 'new-value' ) ;
41
+
42
+ const result = await metadataCache . getOrFetch ( key , queryFn ) ;
43
+
44
+ expect ( result ) . toBe ( value ) ;
45
+ expect ( queryFn ) . not . toHaveBeenCalled ( ) ;
46
+ } ) ;
47
+
48
+ it ( 'should call query function and store result if no cached value exists' , async ( ) => {
49
+ const key = 'test-key' ;
50
+ const expectedValue = { data : 'fetched-data' } ;
51
+ const queryFn = jest . fn ( ) . mockResolvedValue ( expectedValue ) ;
52
+
53
+ const result = await metadataCache . getOrFetch ( key , queryFn ) ;
54
+
55
+ expect ( result ) . toBe ( expectedValue ) ;
56
+ expect ( queryFn ) . toHaveBeenCalledTimes ( 1 ) ;
57
+ expect ( metadataCache . get ( key ) ) . toBe ( expectedValue ) ;
58
+ } ) ;
59
+
60
+ it ( 'should reuse pending promises for the same key' , async ( ) => {
61
+ const key = 'test-key' ;
62
+ let resolvePromise : ( value : any ) => void ;
63
+
64
+ // Create a promise that we can control when it resolves
65
+ const pendingPromise = new Promise ( resolve => {
66
+ resolvePromise = resolve ;
67
+ } ) ;
68
+
69
+ const queryFn = jest . fn ( ) . mockReturnValue ( pendingPromise ) ;
70
+
71
+ // Start two requests for the same key
72
+ const promise1 = metadataCache . getOrFetch ( key , queryFn ) ;
73
+ const promise2 = metadataCache . getOrFetch ( key , queryFn ) ;
74
+
75
+ // The query function should only be called once
76
+ expect ( queryFn ) . toHaveBeenCalledTimes ( 1 ) ;
77
+
78
+ // Now resolve the promise
79
+ resolvePromise ! ( { data : 'result' } ) ;
80
+
81
+ // Both promises should resolve to the same value
82
+ const result1 = await promise1 ;
83
+ const result2 = await promise2 ;
84
+
85
+ expect ( result1 ) . toEqual ( { data : 'result' } ) ;
86
+ expect ( result2 ) . toEqual ( { data : 'result' } ) ;
87
+ expect ( result1 ) . toBe ( result2 ) ; // Should be the same object reference
88
+ } ) ;
89
+
90
+ it ( 'should clean up pending promise after resolution' , async ( ) => {
91
+ const key = 'test-key' ;
92
+ const value = { data : 'test-data' } ;
93
+ const queryFn = jest . fn ( ) . mockResolvedValue ( value ) ;
94
+
95
+ // Access the private pendingQueries map using any type assertion
96
+ const pendingQueriesMap = ( metadataCache as any ) . pendingQueries ;
97
+
98
+ await metadataCache . getOrFetch ( key , queryFn ) ;
99
+
100
+ // After resolution, the pending query should be removed from the map
101
+ expect ( pendingQueriesMap . has ( key ) ) . toBe ( false ) ;
102
+ } ) ;
103
+
104
+ it ( 'should clean up pending promise after rejection' , async ( ) => {
105
+ const key = 'test-key' ;
106
+ const error = new Error ( 'Query failed' ) ;
107
+ const queryFn = jest . fn ( ) . mockRejectedValue ( error ) ;
108
+
109
+ // Access the private pendingQueries map using any type assertion
110
+ const pendingQueriesMap = ( metadataCache as any ) . pendingQueries ;
111
+
112
+ try {
113
+ await metadataCache . getOrFetch ( key , queryFn ) ;
114
+ } catch ( e ) {
115
+ // Expected to throw
116
+ }
117
+
118
+ // After rejection, the pending query should be removed from the map
119
+ expect ( pendingQueriesMap . has ( key ) ) . toBe ( false ) ;
120
+ // And no value should be stored in the cache
121
+ expect ( metadataCache . get ( key ) ) . toBeUndefined ( ) ;
122
+ } ) ;
123
+ } ) ;
124
+ } ) ;
125
+
23
126
describe ( 'Metadata' , ( ) => {
24
127
let metadata : Metadata ;
25
128
0 commit comments