Skip to content

Commit 5a7e69b

Browse files
committed
add test
1 parent fbfea80 commit 5a7e69b

File tree

1 file changed

+152
-5
lines changed

1 file changed

+152
-5
lines changed

test/keyvault.test.ts

Lines changed: 152 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import chaiAsPromised from "chai-as-promised";
77
chai.use(chaiAsPromised);
88
const expect = chai.expect;
99
import { load } from "../src/index.js";
10-
import { sinon, createMockedConnectionString, createMockedTokenCredential, mockAppConfigurationClientListConfigurationSettings, mockSecretClientGetSecret, restoreMocks, createMockedKeyVaultReference, sleepInMs } from "./utils/testHelper.js";
10+
import { sinon, createMockedConnectionString, createMockedTokenCredential, mockAppConfigurationClientListConfigurationSettings, mockAppConfigurationClientGetConfigurationSetting, mockSecretClientGetSecret, restoreMocks, createMockedKeyVaultReference, createMockedKeyValue, sleepInMs } from "./utils/testHelper.js";
1111
import { KeyVaultSecret, SecretClient } from "@azure/keyvault-secrets";
1212
import { ErrorMessages, KeyVaultReferenceErrorMessages } from "../src/common/errorMessages.js";
1313

@@ -144,6 +144,10 @@ describe("key vault reference", function () {
144144

145145
describe("key vault secret refresh", function () {
146146

147+
afterEach(() => {
148+
restoreMocks();
149+
});
150+
147151
beforeEach(() => {
148152
const data = [
149153
["TestKey", "https://fake-vault-name.vault.azure.net/secrets/fakeSecretName", "SecretValue"]
@@ -153,10 +157,6 @@ describe("key vault secret refresh", function () {
153157
mockAppConfigurationClientListConfigurationSettings([kvs]);
154158
});
155159

156-
afterEach(() => {
157-
restoreMocks();
158-
});
159-
160160
it("should not allow secret refresh interval less than 1 minute", async () => {
161161
const connectionString = createMockedConnectionString();
162162
const loadWithInvalidSecretRefreshInterval = load(connectionString, {
@@ -199,4 +199,151 @@ describe("key vault secret refresh", function () {
199199
expect(settings.get("TestKey")).eq("SecretValue - Updated");
200200
});
201201
});
202+
203+
describe("min secret refresh interval during key-value refresh", function () {
204+
let getSecretCallCount = 0;
205+
let sentinelEtag = "initial-etag";
206+
207+
afterEach(() => {
208+
restoreMocks();
209+
getSecretCallCount = 0;
210+
});
211+
212+
/**
213+
* This test verifies the enforcement of the minimum secret refresh interval during key-value refresh.
214+
* When key-value refresh is triggered (by a watched setting change), the provider calls clearCache()
215+
* on the KeyVaultSecretProvider. However, clearCache() only clears the cache if the minimum secret
216+
* refresh interval (60 seconds) has passed. This prevents overwhelming Key Vaults with too many requests.
217+
*/
218+
it("should not re-fetch secrets when key-value refresh happens within min secret refresh interval", async () => {
219+
// Setup: key vault reference + sentinel key for watching
220+
const kvWithSentinel = [
221+
createMockedKeyVaultReference("TestKey", "https://fake-vault-name.vault.azure.net/secrets/fakeSecretName"),
222+
createMockedKeyValue({ key: "sentinel", value: "initialValue", etag: sentinelEtag })
223+
];
224+
mockAppConfigurationClientListConfigurationSettings([kvWithSentinel]);
225+
mockAppConfigurationClientGetConfigurationSetting(kvWithSentinel);
226+
227+
// Mock SecretClient with call counting
228+
const client = new SecretClient("https://fake-vault-name.vault.azure.net", createMockedTokenCredential());
229+
sinon.stub(client, "getSecret").callsFake(async () => {
230+
getSecretCallCount++;
231+
return { value: "SecretValue" } as KeyVaultSecret;
232+
});
233+
234+
// Load with key-value refresh enabled (watching sentinel)
235+
const settings = await load(createMockedConnectionString(), {
236+
refreshOptions: {
237+
enabled: true,
238+
refreshIntervalInMs: 1000, // 1 second refresh interval for key-values
239+
watchedSettings: [{ key: "sentinel" }]
240+
},
241+
keyVaultOptions: {
242+
secretClients: [client]
243+
}
244+
});
245+
246+
expect(settings.get("TestKey")).eq("SecretValue");
247+
expect(getSecretCallCount).eq(1); // Initial load fetched the secret
248+
249+
// Simulate sentinel change to trigger key-value refresh
250+
sentinelEtag = "changed-etag-1";
251+
const updatedKvs = [
252+
createMockedKeyVaultReference("TestKey", "https://fake-vault-name.vault.azure.net/secrets/fakeSecretName"),
253+
createMockedKeyValue({ key: "sentinel", value: "changedValue1", etag: sentinelEtag })
254+
];
255+
restoreMocks();
256+
mockAppConfigurationClientListConfigurationSettings([updatedKvs]);
257+
mockAppConfigurationClientGetConfigurationSetting(updatedKvs);
258+
sinon.stub(client, "getSecret").callsFake(async () => {
259+
getSecretCallCount++;
260+
return { value: "SecretValue" } as KeyVaultSecret;
261+
});
262+
263+
// Wait for refresh interval and trigger refresh
264+
await sleepInMs(1000 + 100);
265+
await settings.refresh();
266+
267+
// Key-value refresh happened, but secret should NOT be re-fetched
268+
// because min secret refresh interval (60s) hasn't passed
269+
expect(getSecretCallCount).eq(1); // Still 1, no additional getSecret call
270+
271+
// Trigger another key-value refresh
272+
sentinelEtag = "changed-etag-2";
273+
const updatedKvs2 = [
274+
createMockedKeyVaultReference("TestKey", "https://fake-vault-name.vault.azure.net/secrets/fakeSecretName"),
275+
createMockedKeyValue({ key: "sentinel", value: "changedValue2", etag: sentinelEtag })
276+
];
277+
restoreMocks();
278+
mockAppConfigurationClientListConfigurationSettings([updatedKvs2]);
279+
mockAppConfigurationClientGetConfigurationSetting(updatedKvs2);
280+
sinon.stub(client, "getSecret").callsFake(async () => {
281+
getSecretCallCount++;
282+
return { value: "SecretValue" } as KeyVaultSecret;
283+
});
284+
285+
await sleepInMs(1000 + 100);
286+
await settings.refresh();
287+
288+
// Still no additional getSecret call due to min interval enforcement
289+
expect(getSecretCallCount).eq(1);
290+
});
291+
292+
it("should re-fetch secrets after min secret refresh interval passes during key-value refresh", async () => {
293+
// Setup: key vault reference + sentinel key for watching
294+
let currentSentinelValue = "initialValue";
295+
sentinelEtag = "initial-etag";
296+
297+
const getKvs = () => [
298+
createMockedKeyVaultReference("TestKey", "https://fake-vault-name.vault.azure.net/secrets/fakeSecretName"),
299+
createMockedKeyValue({ key: "sentinel", value: currentSentinelValue, etag: sentinelEtag })
300+
];
301+
302+
mockAppConfigurationClientListConfigurationSettings([getKvs()]);
303+
mockAppConfigurationClientGetConfigurationSetting(getKvs());
304+
305+
// Mock SecretClient with call counting
306+
const client = new SecretClient("https://fake-vault-name.vault.azure.net", createMockedTokenCredential());
307+
sinon.stub(client, "getSecret").callsFake(async () => {
308+
getSecretCallCount++;
309+
return { value: `SecretValue-${getSecretCallCount}` } as KeyVaultSecret;
310+
});
311+
312+
// Load with key-value refresh enabled
313+
const settings = await load(createMockedConnectionString(), {
314+
refreshOptions: {
315+
enabled: true,
316+
refreshIntervalInMs: 1000,
317+
watchedSettings: [{ key: "sentinel" }]
318+
},
319+
keyVaultOptions: {
320+
secretClients: [client]
321+
}
322+
});
323+
324+
expect(settings.get("TestKey")).eq("SecretValue-1");
325+
expect(getSecretCallCount).eq(1);
326+
327+
// Wait for min secret refresh interval (60 seconds) to pass
328+
await sleepInMs(60_000 + 100);
329+
330+
// Now change sentinel to trigger key-value refresh
331+
currentSentinelValue = "changedValue";
332+
sentinelEtag = "changed-etag";
333+
restoreMocks();
334+
mockAppConfigurationClientListConfigurationSettings([getKvs()]);
335+
mockAppConfigurationClientGetConfigurationSetting(getKvs());
336+
sinon.stub(client, "getSecret").callsFake(async () => {
337+
getSecretCallCount++;
338+
return { value: `SecretValue-${getSecretCallCount}` } as KeyVaultSecret;
339+
});
340+
341+
await sleepInMs(1000 + 100); // Wait for kv refresh interval
342+
await settings.refresh();
343+
344+
// Now getSecret SHOULD be called again because min interval has passed
345+
expect(getSecretCallCount).eq(2);
346+
expect(settings.get("TestKey")).eq("SecretValue-2");
347+
});
348+
});
202349
/* eslint-enable @typescript-eslint/no-unused-expressions */

0 commit comments

Comments
 (0)