Skip to content

Commit 261e72f

Browse files
committed
add refreshOptions.enabled
1 parent 8ba1252 commit 261e72f

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { DefaultRefreshIntervalInMs, MinimumRefreshIntervalInMs } from "./Refres
1515
import { LinkedList } from "./common/linkedList";
1616
import { Disposable } from "./common/disposable";
1717

18-
export class AzureAppConfigurationImpl extends Map<string, unknown> implements AzureAppConfiguration {
18+
export class AzureAppConfigurationImpl extends Map<string, any> implements AzureAppConfiguration {
1919
private adapters: IKeyValueAdapter[] = [];
2020
/**
2121
* Trim key prefixes sorted in descending order.
@@ -25,7 +25,7 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
2525
private readonly requestTracingEnabled: boolean;
2626
// Refresh
2727
private refreshIntervalInMs: number | undefined;
28-
private onRefreshListeners: LinkedList<() => any> | undefined;
28+
private onRefreshListeners: LinkedList<() => any> = new LinkedList();
2929
private lastUpdateTimestamp: number;
3030
private sentinels: ConfigurationSettingId[] | undefined;
3131

@@ -41,26 +41,29 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
4141
this.sortedTrimKeyPrefixes = [...options.trimKeyPrefixes].sort((a, b) => b.localeCompare(a));
4242
}
4343

44-
if (options?.refreshOptions) {
45-
this.onRefreshListeners = new LinkedList();
46-
this.refreshIntervalInMs = DefaultRefreshIntervalInMs;
44+
if (options?.refreshOptions?.enabled) {
45+
const { watchedSettings, refreshIntervalInMs } = options.refreshOptions;
46+
// validate watched settings
47+
if (watchedSettings === undefined || watchedSettings.length === 0) {
48+
throw new Error("Refresh is enabled but no watched settings are specified.");
49+
}
4750

48-
const refreshIntervalInMs = this.options?.refreshOptions?.refreshIntervalInMs;
51+
// process refresh interval
4952
if (refreshIntervalInMs !== undefined) {
5053
if (refreshIntervalInMs < MinimumRefreshIntervalInMs) {
5154
throw new Error(`The refresh interval time cannot be less than ${MinimumRefreshIntervalInMs} milliseconds.`);
5255
} else {
5356
this.refreshIntervalInMs = refreshIntervalInMs;
5457
}
58+
} else {
59+
this.refreshIntervalInMs = DefaultRefreshIntervalInMs;
5560
}
5661

57-
this.sentinels = options.refreshOptions.watchedSettings?.map(setting => {
58-
const key = setting.key;
59-
const label = setting.label;
60-
if (key.includes("*") || label?.includes("*")) {
61-
throw new Error("Wildcard key or label filters are not supported for refresh.");
62+
this.sentinels = watchedSettings.map(setting => {
63+
if (setting.key.includes("*") || setting.label?.includes("*")) {
64+
throw new Error("The character '*' is not supported in key or label.");
6265
}
63-
return { key, label };
66+
return { ...setting };
6467
});
6568
}
6669

@@ -70,6 +73,11 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
7073
this.adapters.push(new JsonKeyValueAdapter());
7174
}
7275

76+
77+
get _refreshEnabled(): boolean {
78+
return !!this.options?.refreshOptions?.enabled;
79+
}
80+
7381
public async load(requestType: RequestType = RequestType.Startup) {
7482
const keyValues: [key: string, value: unknown][] = [];
7583

@@ -144,10 +152,10 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
144152
}
145153

146154
public onRefresh(listener: () => any, thisArg?: any): Disposable {
147-
if (this.onRefreshListeners === undefined) {
148-
// TODO: Add unit tests
149-
throw new Error("Illegal operation because refreshOptions is not provided on loading.");
155+
if (!this._refreshEnabled) {
156+
throw new Error("Refresh is not enabled.");
150157
}
158+
151159
const boundedListener = listener.bind(thisArg);
152160
const remove = this.onRefreshListeners.push(boundedListener);
153161
return new Disposable(remove);

src/RefreshOptions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export const DefaultRefreshIntervalInMs = 30 * 1000;
77
export const MinimumRefreshIntervalInMs = 1 * 1000;
88

99
export interface RefreshOptions {
10+
/**
11+
* Specifies whether the provider should automatically refresh when the configuration is changed.
12+
*/
13+
enabled: boolean;
14+
1015
/**
1116
* Specifies the interval for refresh to really update the values.
1217
* Default value is 30 seconds. Must be greater than 1 second.
@@ -15,7 +20,8 @@ export interface RefreshOptions {
1520
refreshIntervalInMs?: number;
1621

1722
/**
18-
* Specifies settings to be watched, to determine whether the provider triggers a refresh.
23+
* Specifies settings to be watched.
24+
* Only if any of the settings is updated, the refresh will acutally update loaded configuration settings.
1925
*/
20-
watchedSettings: WatchedSetting[];
26+
watchedSettings?: WatchedSetting[];
2127
}

test/refresh.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,36 @@ describe("dynamic refresh", function () {
3838
restoreMocks();
3939
})
4040

41+
it("should only allow non-empty list of watched settings when refresh is enabled", async () => {
42+
const connectionString = createMockedConnectionString();
43+
const loadWithEmptyWatchedSettings = load(connectionString, {
44+
refreshOptions: {
45+
enabled: true,
46+
watchedSettings: []
47+
}
48+
});
49+
const loadWithUndefinedWatchedSettings = load(connectionString, {
50+
refreshOptions: {
51+
enabled: true
52+
}
53+
});
54+
return Promise.all([
55+
expect(loadWithEmptyWatchedSettings).eventually.rejectedWith("Refresh is enabled but no watched settings are specified."),
56+
expect(loadWithUndefinedWatchedSettings).eventually.rejectedWith("Refresh is enabled but no watched settings are specified.")
57+
]);
58+
});
59+
60+
it("should throw error when calling onRefresh when refresh is not enabled", async () => {
61+
const connectionString = createMockedConnectionString();
62+
const settings = await load(connectionString);
63+
expect(() => settings.onRefresh(() => { })).throws("Refresh is not enabled.");
64+
});
65+
4166
it("should only udpate values after refreshInterval", async () => {
4267
const connectionString = createMockedConnectionString();
4368
const settings = await load(connectionString, {
4469
refreshOptions: {
70+
enabled: true,
4571
refreshIntervalInMs: 2000,
4672
watchedSettings: [
4773
{ key: "app.settings.fontColor" }
@@ -69,6 +95,7 @@ describe("dynamic refresh", function () {
6995
const connectionString = createMockedConnectionString();
7096
const settings = await load(connectionString, {
7197
refreshOptions: {
98+
enabled: true,
7299
refreshIntervalInMs: 2000,
73100
watchedSettings: [
74101
{ key: "app.settings.fontColor" }
@@ -89,6 +116,7 @@ describe("dynamic refresh", function () {
89116
const connectionString = createMockedConnectionString();
90117
const settings = await load(connectionString, {
91118
refreshOptions: {
119+
enabled: true,
92120
refreshIntervalInMs: 2000,
93121
watchedSettings: [
94122
{ key: "app.settings.fontColor" },
@@ -113,6 +141,7 @@ describe("dynamic refresh", function () {
113141
const connectionString = createMockedConnectionString();
114142
const settings = await load(connectionString, {
115143
refreshOptions: {
144+
enabled: true,
116145
refreshIntervalInMs: 2000,
117146
watchedSettings: [
118147
{ key: "app.settings.fontColor" }

0 commit comments

Comments
 (0)