@@ -241,12 +241,12 @@ describe("key vault reference deduplication", function () {
241241 expect ( settings . get ( "TestKeyVersioned" ) ) . eq ( "VersionedValue" ) ;
242242 } ) ;
243243
244- it ( "should not cache failures and retry on a subsequent attempt " , async ( ) => {
244+ it ( "should recover and not cache the failure when preload fails to fetch a secret " , async ( ) => {
245245 mockDuplicateReferences ( ) ;
246246 const client = new SecretClient ( "https://fake-vault-name.vault.azure.net" , createMockedTokenCredential ( ) ) ;
247247 const stub = sinon . stub ( client , "getSecret" ) ;
248- // The first (deduplicated) request rejects; the retry attempt succeeds.
249- // If the failure were cached, the retry would never succeed .
248+ // The preload fetch (best-effort) rejects and must not be cached ; the on-demand resolution then
249+ // recovers by re-fetching successfully .
250250 stub . onCall ( 0 ) . callsFake ( async ( ) => {
251251 await sleepInMs ( 100 ) ;
252252 throw new Error ( "Key Vault unavailable" ) ;
@@ -262,14 +262,70 @@ describe("key vault reference deduplication", function () {
262262 }
263263 } ) ;
264264
265- // First round: 5 concurrent references deduped to a single failing request.
266- // Second round (after load retry): a single succeeding request.
267- expect ( stub . callCount ) . eq ( 2 ) ;
265+ // The failed preload is not cached, so all references still resolve successfully.
268266 for ( const key of [ "TestKey1" , "TestKey2" , "TestKey3" , "TestKey4" , "TestKey5" ] ) {
269267 expect ( settings . get ( key ) ) . eq ( "SecretValue" ) ;
270268 }
271269 } ) ;
272270
271+ it ( "should re-fetch once per unique secret on a key-value change reload" , async ( ) => {
272+ const sentinelEtag = "sentinel-etag" ;
273+ const kvs = [
274+ ...[ "TestKey1" , "TestKey2" , "TestKey3" , "TestKey4" , "TestKey5" ] . map ( ( key ) => createMockedKeyVaultReference ( key , sameSecretUri ) ) ,
275+ createMockedKeyValue ( { key : "sentinel" , value : "v1" , etag : sentinelEtag } )
276+ ] ;
277+ mockAppConfigurationClientListConfigurationSettings ( [ kvs ] ) ;
278+ mockAppConfigurationClientGetConfigurationSetting ( kvs ) ;
279+
280+ const client = new SecretClient ( "https://fake-vault-name.vault.azure.net" , createMockedTokenCredential ( ) ) ;
281+ let callCount = 0 ;
282+ sinon . stub ( client , "getSecret" ) . callsFake ( async ( ) => {
283+ callCount ++ ;
284+ await sleepInMs ( 100 ) ;
285+ return { value : `SecretValue-${ callCount } ` } as KeyVaultSecret ;
286+ } ) ;
287+
288+ const settings = await load ( createMockedConnectionString ( ) , {
289+ refreshOptions : {
290+ enabled : true ,
291+ refreshIntervalInMs : 1000 ,
292+ watchedSettings : [ { key : "sentinel" } ]
293+ } ,
294+ keyVaultOptions : {
295+ secretClients : [ client ] ,
296+ parallelSecretResolutionEnabled : true
297+ }
298+ } ) ;
299+ // Initial load resolves the duplicates with a single request.
300+ expect ( callCount ) . eq ( 1 ) ;
301+
302+ // Wait past the min secret refresh interval so the key-value change reload re-fetches secrets.
303+ await sleepInMs ( 60_000 + 100 ) ;
304+
305+ // Trigger a key-value change reload by changing the sentinel.
306+ const updatedKvs = [
307+ ...[ "TestKey1" , "TestKey2" , "TestKey3" , "TestKey4" , "TestKey5" ] . map ( ( key ) => createMockedKeyVaultReference ( key , sameSecretUri ) ) ,
308+ createMockedKeyValue ( { key : "sentinel" , value : "v2" , etag : "sentinel-etag-2" } )
309+ ] ;
310+ restoreMocks ( ) ;
311+ mockAppConfigurationClientListConfigurationSettings ( [ updatedKvs ] ) ;
312+ mockAppConfigurationClientGetConfigurationSetting ( updatedKvs ) ;
313+ let reloadCallCount = 0 ;
314+ sinon . stub ( client , "getSecret" ) . callsFake ( async ( ) => {
315+ reloadCallCount ++ ;
316+ await sleepInMs ( 100 ) ;
317+ return { value : "SecretValue-reloaded" } as KeyVaultSecret ;
318+ } ) ;
319+
320+ await settings . refresh ( ) ;
321+
322+ // The key-value change reload clears the cache and re-fetches, but only once for the unique secret.
323+ expect ( reloadCallCount ) . eq ( 1 ) ;
324+ for ( const key of [ "TestKey1" , "TestKey2" , "TestKey3" , "TestKey4" , "TestKey5" ] ) {
325+ expect ( settings . get ( key ) ) . eq ( "SecretValue-reloaded" ) ;
326+ }
327+ } ) ;
328+
273329 it ( "should re-fetch once per unique secret on each refresh round" , async ( ) => {
274330 mockDuplicateReferences ( ) ;
275331 const client = new SecretClient ( "https://fake-vault-name.vault.azure.net" , createMockedTokenCredential ( ) ) ;
0 commit comments