Skip to content

Commit 647c7a6

Browse files
resolve conflict
2 parents d325127 + f30d237 commit 647c7a6

File tree

8 files changed

+135
-25
lines changed

8 files changed

+135
-25
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6363
#isFailoverRequest: boolean = false;
6464

6565
// Refresh
66+
#refreshInProgress: boolean = false;
67+
6668
#watchAll: boolean = false;
6769
#refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
6870
#onRefreshListeners: Array<() => any> = [];
@@ -273,6 +275,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
273275
throw new Error("Refresh is not enabled for key-values or feature flags.");
274276
}
275277

278+
if (this.#refreshInProgress) {
279+
return;
280+
}
281+
this.#refreshInProgress = true;
282+
try {
283+
await this.#refreshTasks();
284+
} finally {
285+
this.#refreshInProgress = false;
286+
}
287+
}
288+
289+
async #refreshTasks(): Promise<void> {
276290
const refreshTasks: Promise<boolean>[] = [];
277291
if (this.#refreshEnabled) {
278292
refreshTasks.push(this.#refreshKeyValues());

test/featureFlag.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ describe("feature flags", function () {
202202
this.timeout(10000);
203203

204204
before(() => {
205-
mockAppConfigurationClientListConfigurationSettings(mockedKVs);
205+
mockAppConfigurationClientListConfigurationSettings([mockedKVs]);
206206
});
207207

208208
after(() => {

test/json.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe("json", function () {
2020
});
2121

2222
it("should load and parse if content type is application/json", async () => {
23-
mockAppConfigurationClientListConfigurationSettings([jsonKeyValue]);
23+
mockAppConfigurationClientListConfigurationSettings([[jsonKeyValue]]);
2424

2525
const connectionString = createMockedConnectionString();
2626
const settings = await load(connectionString);
@@ -34,7 +34,7 @@ describe("json", function () {
3434
});
3535

3636
it("should not parse key-vault reference", async () => {
37-
mockAppConfigurationClientListConfigurationSettings([jsonKeyValue, keyVaultKeyValue]);
37+
mockAppConfigurationClientListConfigurationSettings([[jsonKeyValue, keyVaultKeyValue]]);
3838

3939
const connectionString = createMockedConnectionString();
4040
const settings = await load(connectionString, {
@@ -50,7 +50,7 @@ describe("json", function () {
5050
});
5151

5252
it("should parse different kinds of legal values", async () => {
53-
mockAppConfigurationClientListConfigurationSettings([
53+
mockAppConfigurationClientListConfigurationSettings([[
5454
/**
5555
* A JSON value MUST be an object, array, number, or string, false, null, true
5656
* See https://www.ietf.org/rfc/rfc4627.txt
@@ -69,7 +69,7 @@ describe("json", function () {
6969
createMockedJsonKeyValue("json.settings.emptyString", ""), // should fail JSON.parse and use string value as fallback
7070
createMockedJsonKeyValue("json.settings.illegalString", "[unclosed"), // should fail JSON.parse
7171

72-
]);
72+
]]);
7373
const connectionString = createMockedConnectionString();
7474
const settings = await load(connectionString);
7575
expect(settings).not.undefined;

test/keyvault.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const mockedData = [
1919
function mockAppConfigurationClient() {
2020
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2121
const kvs = mockedData.map(([key, vaultUri, _value]) => createMockedKeyVaultReference(key, vaultUri));
22-
mockAppConfigurationClientListConfigurationSettings(kvs);
22+
mockAppConfigurationClientListConfigurationSettings([kvs]);
2323
}
2424

2525
function mockNewlyCreatedKeyVaultSecretClients() {

test/load.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe("load", function () {
8080
this.timeout(10000);
8181

8282
before(() => {
83-
mockAppConfigurationClientListConfigurationSettings(mockedKVs);
83+
mockAppConfigurationClientListConfigurationSettings([mockedKVs]);
8484
});
8585

8686
after(() => {

test/refresh.test.ts

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ function addSetting(key: string, value: any) {
2323
mockedKVs.push(createMockedKeyValue({ key, value }));
2424
}
2525

26+
let listKvRequestCount = 0;
27+
const listKvCallback = () => {
28+
listKvRequestCount++;
29+
};
30+
let getKvRequestCount = 0;
31+
const getKvCallback = () => {
32+
getKvRequestCount++;
33+
};
34+
2635
describe("dynamic refresh", function () {
2736
this.timeout(10000);
2837

@@ -32,12 +41,14 @@ describe("dynamic refresh", function () {
3241
{ value: "40", key: "app.settings.fontSize" },
3342
{ value: "30", key: "app.settings.fontSize", label: "prod" }
3443
].map(createMockedKeyValue);
35-
mockAppConfigurationClientListConfigurationSettings(mockedKVs);
36-
mockAppConfigurationClientGetConfigurationSetting(mockedKVs);
44+
mockAppConfigurationClientListConfigurationSettings([mockedKVs], listKvCallback);
45+
mockAppConfigurationClientGetConfigurationSetting(mockedKVs, getKvCallback);
3746
});
3847

3948
afterEach(() => {
4049
restoreMocks();
50+
listKvRequestCount = 0;
51+
getKvRequestCount = 0;
4152
});
4253

4354
it("should throw error when refresh is not enabled but refresh is called", async () => {
@@ -120,6 +131,8 @@ describe("dynamic refresh", function () {
120131
]
121132
}
122133
});
134+
expect(listKvRequestCount).eq(1);
135+
expect(getKvRequestCount).eq(0);
123136
expect(settings).not.undefined;
124137
expect(settings.get("app.settings.fontColor")).eq("red");
125138
expect(settings.get("app.settings.fontSize")).eq("40");
@@ -130,10 +143,14 @@ describe("dynamic refresh", function () {
130143
// within refreshInterval, should not really refresh
131144
await settings.refresh();
132145
expect(settings.get("app.settings.fontColor")).eq("red");
146+
expect(listKvRequestCount).eq(1); // no more request should be sent during the refresh interval
147+
expect(getKvRequestCount).eq(0); // no more request should be sent during the refresh interval
133148

134149
// after refreshInterval, should really refresh
135150
await sleepInMs(2 * 1000 + 1);
136151
await settings.refresh();
152+
expect(listKvRequestCount).eq(2);
153+
expect(getKvRequestCount).eq(1);
137154
expect(settings.get("app.settings.fontColor")).eq("blue");
138155
});
139156

@@ -148,18 +165,22 @@ describe("dynamic refresh", function () {
148165
]
149166
}
150167
});
168+
expect(listKvRequestCount).eq(1);
169+
expect(getKvRequestCount).eq(0);
151170
expect(settings).not.undefined;
152171
expect(settings.get("app.settings.fontColor")).eq("red");
153172
expect(settings.get("app.settings.fontSize")).eq("40");
154173

155174
// delete setting 'app.settings.fontColor'
156175
const newMockedKVs = mockedKVs.filter(elem => elem.key !== "app.settings.fontColor");
157176
restoreMocks();
158-
mockAppConfigurationClientListConfigurationSettings(newMockedKVs);
159-
mockAppConfigurationClientGetConfigurationSetting(newMockedKVs);
177+
mockAppConfigurationClientListConfigurationSettings([newMockedKVs], listKvCallback);
178+
mockAppConfigurationClientGetConfigurationSetting(newMockedKVs, getKvCallback);
160179

161180
await sleepInMs(2 * 1000 + 1);
162181
await settings.refresh();
182+
expect(listKvRequestCount).eq(2);
183+
expect(getKvRequestCount).eq(2); // one conditional request to detect change and one request as part of loading all kvs (because app.settings.fontColor doesn't exist in the response of listKv request)
163184
expect(settings.get("app.settings.fontColor")).eq(undefined);
164185
});
165186

@@ -174,13 +195,17 @@ describe("dynamic refresh", function () {
174195
]
175196
}
176197
});
198+
expect(listKvRequestCount).eq(1);
199+
expect(getKvRequestCount).eq(0);
177200
expect(settings).not.undefined;
178201
expect(settings.get("app.settings.fontColor")).eq("red");
179202
expect(settings.get("app.settings.fontSize")).eq("40");
180203

181204
updateSetting("app.settings.fontSize", "50"); // unwatched setting
182205
await sleepInMs(2 * 1000 + 1);
183206
await settings.refresh();
207+
expect(listKvRequestCount).eq(1);
208+
expect(getKvRequestCount).eq(1);
184209
expect(settings.get("app.settings.fontSize")).eq("40");
185210
});
186211

@@ -196,6 +221,8 @@ describe("dynamic refresh", function () {
196221
]
197222
}
198223
});
224+
expect(listKvRequestCount).eq(1);
225+
expect(getKvRequestCount).eq(0);
199226
expect(settings).not.undefined;
200227
expect(settings.get("app.settings.fontColor")).eq("red");
201228
expect(settings.get("app.settings.fontSize")).eq("40");
@@ -205,6 +232,8 @@ describe("dynamic refresh", function () {
205232
updateSetting("app.settings.fontSize", "50");
206233
await sleepInMs(2 * 1000 + 1);
207234
await settings.refresh();
235+
expect(listKvRequestCount).eq(2);
236+
expect(getKvRequestCount).eq(2); // two getKv request for two watched settings
208237
expect(settings.get("app.settings.fontSize")).eq("50");
209238
expect(settings.get("app.settings.bgColor")).eq("white");
210239
});
@@ -290,6 +319,8 @@ describe("dynamic refresh", function () {
290319
]
291320
}
292321
});
322+
expect(listKvRequestCount).eq(1);
323+
expect(getKvRequestCount).eq(1); // app.settings.bgColor doesn't exist in the response of listKv request, so an additional getKv request is made to get it.
293324
expect(settings).not.undefined;
294325
expect(settings.get("app.settings.fontColor")).eq("red");
295326
expect(settings.get("app.settings.fontSize")).eq("40");
@@ -298,6 +329,8 @@ describe("dynamic refresh", function () {
298329
updateSetting("app.settings.fontColor", "blue");
299330
await sleepInMs(2 * 1000 + 1);
300331
await settings.refresh();
332+
expect(listKvRequestCount).eq(1);
333+
expect(getKvRequestCount).eq(2);
301334
// should not refresh
302335
expect(settings.get("app.settings.fontColor")).eq("red");
303336
});
@@ -310,6 +343,8 @@ describe("dynamic refresh", function () {
310343
refreshIntervalInMs: 2000
311344
}
312345
});
346+
expect(listKvRequestCount).eq(1);
347+
expect(getKvRequestCount).eq(0);
313348
expect(settings).not.undefined;
314349
expect(settings.get("app.settings.fontColor")).eq("red");
315350
expect(settings.get("app.settings.fontSize")).eq("40");
@@ -320,6 +355,8 @@ describe("dynamic refresh", function () {
320355
// after refreshInterval, should really refresh
321356
await sleepInMs(2 * 1000 + 1);
322357
await settings.refresh();
358+
expect(listKvRequestCount).eq(3); // 1 + 2 more requests: one conditional request to detect change and one request to reload all key values
359+
expect(getKvRequestCount).eq(0);
323360
expect(settings.get("app.settings.fontColor")).eq("blue");
324361
});
325362

@@ -331,6 +368,8 @@ describe("dynamic refresh", function () {
331368
refreshIntervalInMs: 2000
332369
}
333370
});
371+
expect(listKvRequestCount).eq(1);
372+
expect(getKvRequestCount).eq(0);
334373

335374
let refreshSuccessfulCount = 0;
336375
settings.onRefresh(() => {
@@ -342,22 +381,60 @@ describe("dynamic refresh", function () {
342381

343382
await sleepInMs(2 * 1000 + 1);
344383
await settings.refresh();
345-
expect(refreshSuccessfulCount).eq(0); // no change in feature flags, because page etags are the same.
384+
expect(listKvRequestCount).eq(2); // one more conditional request to detect change
385+
expect(getKvRequestCount).eq(0);
386+
expect(refreshSuccessfulCount).eq(0); // no change in key values, because page etags are the same.
346387

347388
// change key value
348389
restoreMocks();
349390
const changedKVs = [
350391
{ value: "blue", key: "app.settings.fontColor" },
351392
{ value: "40", key: "app.settings.fontSize" }
352393
].map(createMockedKeyValue);
353-
mockAppConfigurationClientListConfigurationSettings(changedKVs);
354-
mockAppConfigurationClientGetConfigurationSetting(changedKVs);
394+
mockAppConfigurationClientListConfigurationSettings([changedKVs], listKvCallback);
395+
mockAppConfigurationClientGetConfigurationSetting(changedKVs, getKvCallback);
355396

356397
await sleepInMs(2 * 1000 + 1);
357398
await settings.refresh();
399+
expect(listKvRequestCount).eq(4); // 2 + 2 more requests: one conditional request to detect change and one request to reload all key values
400+
expect(getKvRequestCount).eq(0);
358401
expect(refreshSuccessfulCount).eq(1); // change in key values, because page etags are different.
359402
expect(settings.get("app.settings.fontColor")).eq("blue");
360403
});
404+
405+
it("should not refresh any more when there is refresh in progress", async () => {
406+
const connectionString = createMockedConnectionString();
407+
const settings = await load(connectionString, {
408+
refreshOptions: {
409+
enabled: true,
410+
refreshIntervalInMs: 2000,
411+
watchedSettings: [
412+
{ key: "app.settings.fontColor" }
413+
]
414+
}
415+
});
416+
expect(listKvRequestCount).eq(1);
417+
expect(getKvRequestCount).eq(0);
418+
expect(settings).not.undefined;
419+
expect(settings.get("app.settings.fontColor")).eq("red");
420+
421+
// change setting
422+
updateSetting("app.settings.fontColor", "blue");
423+
424+
// after refreshInterval, should really refresh
425+
await sleepInMs(2 * 1000 + 1);
426+
for (let i = 0; i < 5; i++) { // in practice, refresh should not be used in this way
427+
settings.refresh(); // refresh "concurrently"
428+
}
429+
expect(listKvRequestCount).to.be.at.most(2);
430+
expect(getKvRequestCount).to.be.at.most(1);
431+
432+
await sleepInMs(1000); // wait for all 5 refresh attempts to finish
433+
434+
expect(listKvRequestCount).eq(2);
435+
expect(getKvRequestCount).eq(1);
436+
expect(settings.get("app.settings.fontColor")).eq("blue");
437+
});
361438
});
362439

363440
describe("dynamic refresh feature flags", function () {
@@ -368,14 +445,16 @@ describe("dynamic refresh feature flags", function () {
368445

369446
afterEach(() => {
370447
restoreMocks();
448+
listKvRequestCount = 0;
449+
getKvRequestCount = 0;
371450
});
372451

373452
it("should refresh feature flags when enabled", async () => {
374453
mockedKVs = [
375454
createMockedFeatureFlag("Beta", { enabled: true })
376455
];
377-
mockAppConfigurationClientListConfigurationSettings(mockedKVs);
378-
mockAppConfigurationClientGetConfigurationSetting(mockedKVs);
456+
mockAppConfigurationClientListConfigurationSettings([mockedKVs], listKvCallback);
457+
mockAppConfigurationClientGetConfigurationSetting(mockedKVs, getKvCallback);
379458

380459
const connectionString = createMockedConnectionString();
381460
const settings = await load(connectionString, {
@@ -390,6 +469,8 @@ describe("dynamic refresh feature flags", function () {
390469
}
391470
}
392471
});
472+
expect(listKvRequestCount).eq(2); // one listKv request for kvs and one listKv request for feature flags
473+
expect(getKvRequestCount).eq(0);
393474
expect(settings).not.undefined;
394475
expect(settings.get("feature_management")).not.undefined;
395476
expect(settings.get<any>("feature_management").feature_flags).not.undefined;
@@ -408,6 +489,8 @@ describe("dynamic refresh feature flags", function () {
408489

409490
await sleepInMs(2 * 1000 + 1);
410491
await settings.refresh();
492+
expect(listKvRequestCount).eq(4); // 2 + 2 more requests: one conditional request to detect change and one request to reload all feature flags
493+
expect(getKvRequestCount).eq(0);
411494

412495
expect(settings.get<any>("feature_management").feature_flags[0].id).eq("Beta");
413496
expect(settings.get<any>("feature_management").feature_flags[0].enabled).eq(false);
@@ -424,8 +507,8 @@ describe("dynamic refresh feature flags", function () {
424507
createMockedFeatureFlag("Beta_1", { enabled: true }),
425508
createMockedFeatureFlag("Beta_2", { enabled: true }),
426509
];
427-
mockAppConfigurationClientListConfigurationSettings(page1, page2);
428-
mockAppConfigurationClientGetConfigurationSetting([...page1, ...page2]);
510+
mockAppConfigurationClientListConfigurationSettings([page1, page2], listKvCallback);
511+
mockAppConfigurationClientGetConfigurationSetting([...page1, ...page2], getKvCallback);
429512

430513
const connectionString = createMockedConnectionString();
431514
const settings = await load(connectionString, {
@@ -440,6 +523,8 @@ describe("dynamic refresh feature flags", function () {
440523
}
441524
}
442525
});
526+
expect(listKvRequestCount).eq(2);
527+
expect(getKvRequestCount).eq(0);
443528

444529
let refreshSuccessfulCount = 0;
445530
settings.onRefresh(() => {
@@ -448,16 +533,20 @@ describe("dynamic refresh feature flags", function () {
448533

449534
await sleepInMs(2 * 1000 + 1);
450535
await settings.refresh();
536+
expect(listKvRequestCount).eq(3); // one conditional request to detect change
537+
expect(getKvRequestCount).eq(0);
451538
expect(refreshSuccessfulCount).eq(0); // no change in feature flags, because page etags are the same.
452539

453540
// change feature flag Beta_1 to false
454541
page2[0] = createMockedFeatureFlag("Beta_1", { enabled: false });
455542
restoreMocks();
456-
mockAppConfigurationClientListConfigurationSettings(page1, page2);
457-
mockAppConfigurationClientGetConfigurationSetting([...page1, ...page2]);
543+
mockAppConfigurationClientListConfigurationSettings([page1, page2], listKvCallback);
544+
mockAppConfigurationClientGetConfigurationSetting([...page1, ...page2], getKvCallback);
458545

459546
await sleepInMs(2 * 1000 + 1);
460547
await settings.refresh();
548+
expect(listKvRequestCount).eq(5); // 3 + 2 more requests: one conditional request to detect change and one request to reload all feature flags
549+
expect(getKvRequestCount).eq(0);
461550
expect(refreshSuccessfulCount).eq(1); // change in feature flags, because page etags are different.
462551
});
463552
});

0 commit comments

Comments
 (0)