11import type { RelayerGetResponseKeyUrlSnakeCase } from '../types/private' ;
2- import { getKeysFromRelayer } from './networkV1' ;
2+ import { getKeysFromRelayer , _clearKeyurlCache } from './networkV1' ;
33import { tfheCompactPkeCrsBytes , tfheCompactPublicKeyBytes } from '../../test' ;
44import { SERIALIZED_SIZE_LIMIT_PK } from '../../sdk/lowlevel/constants' ;
55import fetchMock from 'fetch-mock' ;
@@ -134,9 +134,22 @@ const payload: RelayerGetResponseKeyUrlSnakeCase = {
134134const describeIfFetchMock =
135135 TEST_CONFIG . type === 'fetch-mock' ? describe : describe . skip ;
136136
137+ const pubKeyUrl = payload . response . fhe_key_info [ 0 ] . fhe_public_key . urls [ 0 ] ;
138+ const crsUrl = payload . response . crs [ '2048' ] . urls [ 0 ] ;
139+
137140////////////////////////////////////////////////////////////////////////////////
138141
139142describeIfFetchMock ( 'network' , ( ) => {
143+ beforeEach ( ( ) => {
144+ _clearKeyurlCache ( ) ;
145+ fetchMock . removeRoutes ( ) ;
146+ } ) ;
147+
148+ afterEach ( ( ) => {
149+ _clearKeyurlCache ( ) ;
150+ fetchMock . removeRoutes ( ) ;
151+ } ) ;
152+
140153 it ( 'getKeysFromRelayer' , async ( ) => {
141154 fetchMock . get ( 'https://test-relayer.net/v1/keyurl' , payload ) ;
142155
@@ -156,4 +169,58 @@ describeIfFetchMock('network', () => {
156169 material . publicKey . safe_serialize ( SERIALIZED_SIZE_LIMIT_PK ) ,
157170 ) . toStrictEqual ( tfheCompactPublicKeyBytes ) ;
158171 } ) ;
172+
173+ it ( 'cache hit: second call returns same object, /keyurl called once' , async ( ) => {
174+ let keyurlCallCount = 0 ;
175+ fetchMock . get ( TEST_CONFIG . v1 . urls . keyUrl , ( ) => {
176+ keyurlCallCount ++ ;
177+ return payload ;
178+ } ) ;
179+ fetchMock . get ( pubKeyUrl , tfheCompactPublicKeyBytes ) ;
180+ fetchMock . get ( crsUrl , tfheCompactPkeCrsBytes ) ;
181+
182+ const r1 = await getKeysFromRelayer ( TEST_CONFIG . v1 . urls . base ) ;
183+ const r2 = await getKeysFromRelayer ( TEST_CONFIG . v1 . urls . base ) ;
184+
185+ expect ( r1 ) . toBe ( r2 ) ;
186+ expect ( keyurlCallCount ) . toBe ( 1 ) ;
187+ } ) ;
188+
189+ it ( 'FIFO eviction: 17th URL evicts url-00' , async ( ) => {
190+ const keyurlCallCounts : number [ ] = new Array ( 17 ) . fill ( 0 ) as number [ ] ;
191+
192+ // Asset mocks registered once — all 17 payloads reference the same asset URLs
193+ fetchMock . get ( pubKeyUrl , tfheCompactPublicKeyBytes ) ;
194+ fetchMock . get ( crsUrl , tfheCompactPkeCrsBytes ) ;
195+
196+ // Register 17 unique keyurl mocks
197+ for ( let n = 0 ; n < 17 ; n ++ ) {
198+ const idx = n ;
199+ fetchMock . get (
200+ `https://test-relayer.net/url-${ String ( idx ) . padStart ( 2 , '0' ) } /keyurl` ,
201+ ( ) => {
202+ keyurlCallCounts [ idx ] ++ ;
203+ return payload ;
204+ } ,
205+ ) ;
206+ }
207+
208+ // Fill cache with url-00..url-15 (16 entries)
209+ for ( let n = 0 ; n < 16 ; n ++ ) {
210+ await getKeysFromRelayer (
211+ `https://test-relayer.net/url-${ String ( n ) . padStart ( 2 , '0' ) } ` ,
212+ ) ;
213+ }
214+
215+ // Insert url-16 — evicts url-00
216+ await getKeysFromRelayer ( 'https://test-relayer.net/url-16' ) ;
217+
218+ // url-01 is still in cache (only url-00 was evicted)
219+ await getKeysFromRelayer ( 'https://test-relayer.net/url-01' ) ;
220+ expect ( keyurlCallCounts [ 1 ] ) . toBe ( 1 ) ;
221+
222+ // Re-fetch url-00 — was evicted, must hit network again
223+ await getKeysFromRelayer ( 'https://test-relayer.net/url-00' ) ;
224+ expect ( keyurlCallCounts [ 0 ] ) . toBe ( 2 ) ;
225+ } ) ;
159226} ) ;
0 commit comments