Skip to content

Commit 35adc57

Browse files
authored
Merge branch 'main' into yanzh/support-both-map-object
2 parents 819c5fb + 45c8266 commit 35adc57

File tree

8 files changed

+77
-64
lines changed

8 files changed

+77
-64
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
232232
/**
233233
* Refresh the configuration store.
234234
*/
235-
public async refresh(): Promise<void> {
235+
async refresh(): Promise<void> {
236236
if (!this.#refreshEnabled) {
237237
throw new Error("Refresh is not enabled.");
238238
}

src/JsonKeyValueAdapter.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@
44
import { ConfigurationSetting, secretReferenceContentType } from "@azure/app-configuration";
55
import { IKeyValueAdapter } from "./IKeyValueAdapter";
66

7-
87
export class JsonKeyValueAdapter implements IKeyValueAdapter {
9-
private static readonly ExcludedJsonContentTypes: string[] = [
8+
static readonly #ExcludedJsonContentTypes: string[] = [
109
secretReferenceContentType
1110
// TODO: exclude application/vnd.microsoft.appconfig.ff+json after feature management is supported
1211
];
1312

14-
public canProcess(setting: ConfigurationSetting): boolean {
13+
canProcess(setting: ConfigurationSetting): boolean {
1514
if (!setting.contentType) {
1615
return false;
1716
}
18-
if (JsonKeyValueAdapter.ExcludedJsonContentTypes.includes(setting.contentType)) {
17+
if (JsonKeyValueAdapter.#ExcludedJsonContentTypes.includes(setting.contentType)) {
1918
return false;
2019
}
2120
return isJsonContentType(setting.contentType);
2221
}
2322

24-
public async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
23+
async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
2524
let parsedValue: unknown;
2625
if (setting.value !== undefined) {
2726
try {

src/common/disposable.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// Licensed under the MIT license.
33

44
export class Disposable {
5-
private disposed = false;
6-
constructor(private callOnDispose: () => any) { }
5+
#disposed = false;
6+
#callOnDispose: () => any;
7+
8+
constructor(callOnDispose: () => any) {
9+
this.#callOnDispose = callOnDispose;
10+
}
711

812
dispose() {
9-
if (!this.disposed) {
10-
this.callOnDispose();
13+
if (!this.#disposed) {
14+
this.#callOnDispose();
1115
}
12-
this.disposed = true;
16+
this.#disposed = true;
1317
}
14-
1518
}

src/keyvault/AzureKeyVaultKeyValueAdapter.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
1010
/**
1111
* Map vault hostname to corresponding secret client.
1212
*/
13-
private secretClients: Map<string, SecretClient>;
13+
#secretClients: Map<string, SecretClient>;
14+
#keyVaultOptions: KeyVaultOptions | undefined;
1415

15-
constructor(
16-
private keyVaultOptions: KeyVaultOptions | undefined
17-
) { }
16+
constructor(keyVaultOptions: KeyVaultOptions | undefined) {
17+
this.#keyVaultOptions = keyVaultOptions;
18+
}
1819

19-
public canProcess(setting: ConfigurationSetting): boolean {
20+
canProcess(setting: ConfigurationSetting): boolean {
2021
return isSecretReference(setting);
2122
}
2223

23-
public async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
24+
async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
2425
// TODO: cache results to save requests.
25-
if (!this.keyVaultOptions) {
26+
if (!this.#keyVaultOptions) {
2627
throw new Error("Configure keyVaultOptions to resolve Key Vault Reference(s).");
2728
}
2829

@@ -31,37 +32,37 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
3132
parseSecretReference(setting).value.secretId
3233
);
3334

34-
const client = this.getSecretClient(new URL(vaultUrl));
35+
const client = this.#getSecretClient(new URL(vaultUrl));
3536
if (client) {
3637
// TODO: what if error occurs when reading a key vault value? Now it breaks the whole load.
3738
const secret = await client.getSecret(secretName, { version });
3839
return [setting.key, secret.value];
3940
}
4041

41-
if (this.keyVaultOptions.secretResolver) {
42-
return [setting.key, await this.keyVaultOptions.secretResolver(new URL(sourceId))];
42+
if (this.#keyVaultOptions.secretResolver) {
43+
return [setting.key, await this.#keyVaultOptions.secretResolver(new URL(sourceId))];
4344
}
4445

4546
throw new Error("No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
4647
}
4748

48-
private getSecretClient(vaultUrl: URL): SecretClient | undefined {
49-
if (this.secretClients === undefined) {
50-
this.secretClients = new Map();
51-
for (const c of this.keyVaultOptions?.secretClients ?? []) {
52-
this.secretClients.set(getHost(c.vaultUrl), c);
49+
#getSecretClient(vaultUrl: URL): SecretClient | undefined {
50+
if (this.#secretClients === undefined) {
51+
this.#secretClients = new Map();
52+
for (const c of this.#keyVaultOptions?.secretClients ?? []) {
53+
this.#secretClients.set(getHost(c.vaultUrl), c);
5354
}
5455
}
5556

5657
let client: SecretClient | undefined;
57-
client = this.secretClients.get(vaultUrl.host);
58+
client = this.#secretClients.get(vaultUrl.host);
5859
if (client !== undefined) {
5960
return client;
6061
}
6162

62-
if (this.keyVaultOptions?.credential) {
63-
client = new SecretClient(vaultUrl.toString(), this.keyVaultOptions.credential);
64-
this.secretClients.set(vaultUrl.host, client);
63+
if (this.#keyVaultOptions?.credential) {
64+
client = new SecretClient(vaultUrl.toString(), this.#keyVaultOptions.credential);
65+
this.#secretClients.set(vaultUrl.host, client);
6566
return client;
6667
}
6768

src/load.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function load(
4848
endpoint = new URL(endpoint);
4949
} catch (error) {
5050
if (error.code === "ERR_INVALID_URL") {
51-
throw new Error("Invalid Endpoint URL.", { cause: error });
51+
throw new Error("Invalid endpoint URL.", { cause: error });
5252
} else {
5353
throw error;
5454
}

src/refresh/RefreshTimer.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,52 +22,55 @@ const MaxSafeExponential = 30; // Used to avoid overflow. bitwise operations in
2222
const JitterRatio = 0.25;
2323

2424
export class RefreshTimer {
25-
private _minBackoff: number = MinimumBackoffInMs;
26-
private _maxBackoff: number = MaximumBackoffInMs;
27-
private _failedAttempts: number = 0;
28-
private _backoffEnd: number; // Timestamp
25+
#minBackoff: number = MinimumBackoffInMs;
26+
#maxBackoff: number = MaximumBackoffInMs;
27+
#failedAttempts: number = 0;
28+
#backoffEnd: number; // Timestamp
29+
#interval: number;
30+
2931
constructor(
30-
private _interval: number
32+
interval: number
3133
) {
32-
if (this._interval <= 0) {
33-
throw new Error(`Refresh interval must be greater than 0. Given: ${this._interval}`);
34+
if (interval <= 0) {
35+
throw new Error(`Refresh interval must be greater than 0. Given: ${this.#interval}`);
3436
}
3537

36-
this._backoffEnd = Date.now() + this._interval;
38+
this.#interval = interval;
39+
this.#backoffEnd = Date.now() + this.#interval;
3740
}
3841

39-
public canRefresh(): boolean {
40-
return Date.now() >= this._backoffEnd;
42+
canRefresh(): boolean {
43+
return Date.now() >= this.#backoffEnd;
4144
}
4245

43-
public backoff(): void {
44-
this._failedAttempts += 1;
45-
this._backoffEnd = Date.now() + this._calculateBackoffTime();
46+
backoff(): void {
47+
this.#failedAttempts += 1;
48+
this.#backoffEnd = Date.now() + this.#calculateBackoffTime();
4649
}
4750

48-
public reset(): void {
49-
this._failedAttempts = 0;
50-
this._backoffEnd = Date.now() + this._interval;
51+
reset(): void {
52+
this.#failedAttempts = 0;
53+
this.#backoffEnd = Date.now() + this.#interval;
5154
}
5255

53-
private _calculateBackoffTime(): number {
56+
#calculateBackoffTime(): number {
5457
let minBackoffMs: number;
5558
let maxBackoffMs: number;
56-
if (this._interval <= this._minBackoff) {
57-
return this._interval;
59+
if (this.#interval <= this.#minBackoff) {
60+
return this.#interval;
5861
}
5962

6063
// _minBackoff <= _interval
61-
if (this._interval <= this._maxBackoff) {
62-
minBackoffMs = this._minBackoff;
63-
maxBackoffMs = this._interval;
64+
if (this.#interval <= this.#maxBackoff) {
65+
minBackoffMs = this.#minBackoff;
66+
maxBackoffMs = this.#interval;
6467
} else {
65-
minBackoffMs = this._minBackoff;
66-
maxBackoffMs = this._maxBackoff;
68+
minBackoffMs = this.#minBackoff;
69+
maxBackoffMs = this.#maxBackoff;
6770
}
6871

6972
// exponential: minBackoffMs * 2^(failedAttempts-1)
70-
const exponential = Math.min(this._failedAttempts - 1, MaxSafeExponential);
73+
const exponential = Math.min(this.#failedAttempts - 1, MaxSafeExponential);
7174
let calculatedBackoffMs = minBackoffMs * (1 << exponential);
7275
if (calculatedBackoffMs > maxBackoffMs) {
7376
calculatedBackoffMs = maxBackoffMs;

test/keyvault.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ function mockNewlyCreatedKeyVaultSecretClients() {
2727
mockSecretClientGetSecret(mockedData.map(([_key, secretUri, value]) => [secretUri, value]));
2828
}
2929
describe("key vault reference", function () {
30+
this.timeout(10000);
31+
3032
beforeEach(() => {
3133
mockAppConfigurationClient();
3234
mockNewlyCreatedKeyVaultSecretClients();
@@ -37,7 +39,7 @@ describe("key vault reference", function () {
3739
});
3840

3941
it("require key vault options to resolve reference", async () => {
40-
expect(load(createMockedConnectionString())).eventually.rejected;
42+
return expect(load(createMockedConnectionString())).eventually.rejectedWith("Configure keyVaultOptions to resolve Key Vault Reference(s).");
4143
});
4244

4345
it("should resolve key vault reference with credential", async () => {
@@ -93,7 +95,7 @@ describe("key vault reference", function () {
9395
]
9496
}
9597
});
96-
expect(loadKeyVaultPromise).eventually.rejected;
98+
return expect(loadKeyVaultPromise).eventually.rejectedWith("No key vault credential or secret resolver callback configured, and no matching secret client could be found.");
9799
});
98100

99101
it("should fallback to use default credential when corresponding secret client not provided", async () => {

test/load.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const mockedKVs = [{
4141
}].map(createMockedKeyValue);
4242

4343
describe("load", function () {
44+
this.timeout(10000);
45+
4446
before(() => {
4547
mockAppConfigurationClientListConfigurationSettings(mockedKVs);
4648
});
@@ -75,12 +77,12 @@ describe("load", function () {
7577
});
7678

7779
it("should throw error given invalid connection string", async () => {
78-
expect(load("invalid-connection-string")).eventually.rejected;
80+
return expect(load("invalid-connection-string")).eventually.rejectedWith("Invalid connection string.");
7981
});
8082

8183
it("should throw error given invalid endpoint URL", async () => {
8284
const credential = createMockedTokenCredential();
83-
expect(load("invalid-endpoint-url", credential)).eventually.rejected;
85+
return expect(load("invalid-endpoint-url", credential)).eventually.rejectedWith("Invalid endpoint URL.");
8486
});
8587

8688
it("should trim key prefix if applicable", async () => {
@@ -119,23 +121,26 @@ describe("load", function () {
119121
expect(settings.get("KeyForEmptyValue")).eq("");
120122
});
121123

122-
it("should not support * or , in label filters", async () => {
124+
it("should not support * in label filters", async () => {
123125
const connectionString = createMockedConnectionString();
124126
const loadWithWildcardLabelFilter = load(connectionString, {
125127
selectors: [{
126128
keyFilter: "app.*",
127129
labelFilter: "*"
128130
}]
129131
});
130-
expect(loadWithWildcardLabelFilter).to.eventually.rejected;
132+
return expect(loadWithWildcardLabelFilter).to.eventually.rejectedWith("The characters '*' and ',' are not supported in label filters.");
133+
});
131134

135+
it("should not support , in label filters", async () => {
136+
const connectionString = createMockedConnectionString();
132137
const loadWithMultipleLabelFilter = load(connectionString, {
133138
selectors: [{
134139
keyFilter: "app.*",
135140
labelFilter: "labelA,labelB"
136141
}]
137142
});
138-
expect(loadWithMultipleLabelFilter).to.eventually.rejected;
143+
return expect(loadWithMultipleLabelFilter).to.eventually.rejectedWith("The characters '*' and ',' are not supported in label filters.");
139144
});
140145

141146
it("should override config settings with same key but different label", async () => {

0 commit comments

Comments
 (0)