Skip to content

Commit 43d1730

Browse files
add test
1 parent 7348a0c commit 43d1730

File tree

7 files changed

+143
-27
lines changed

7 files changed

+143
-27
lines changed

src/appConfigurationImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
CLIENT_FILTERS_KEY_NAME
4242
} from "./featureManagement/constants.js";
4343
import { FM_PACKAGE_NAME, AI_MIME_PROFILE, AI_CHAT_COMPLETION_MIME_PROFILE } from "./requestTracing/constants.js";
44-
import { parseContentType, isJsonContentType, isFeatureFlagContentType, isSecretReferenceContentType, isSnapshotReferenceContentType } from "./common/contentType.js";
44+
import { parseContentType, isJsonContentType, isFeatureFlagContentType, isSecretReferenceContentType } from "./common/contentType.js";
4545
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/keyVaultKeyValueAdapter.js";
4646
import { RefreshTimer } from "./refresh/refreshTimer.js";
4747
import {

src/common/contentType.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,3 @@ export function isSecretReferenceContentType(contentType: ContentType | undefine
6060
}
6161
return mediaType === secretReferenceContentType;
6262
}
63-
64-
export function isSnapshotReferenceContentType(contentType: ContentType | undefined): boolean {
65-
const mediaType = contentType?.mediaType;
66-
if (!mediaType) {
67-
return false;
68-
}
69-
// TODO: replace with constant when available in Azure SDK
70-
return mediaType === "application/json; profile=\"https://azconfig.io/mime-profiles/snapshot-ref\"; charset=utf-8";
71-
}

test/featureFlag.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,14 @@ describe("feature flags", function () {
415415

416416
it("should load feature flags from snapshot", async () => {
417417
const snapshotName = "Test";
418-
mockAppConfigurationClientGetSnapshot(snapshotName, {compositionType: "key"});
419-
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotName, [[createMockedFeatureFlag("TestFeature", { enabled: true })]]);
418+
const snapshotResponses = new Map([
419+
[snapshotName, { compositionType: "key" }]
420+
]);
421+
const snapshotKVs = new Map([
422+
[snapshotName, [[createMockedFeatureFlag("TestFeature", { enabled: true })]]]
423+
]);
424+
mockAppConfigurationClientGetSnapshot(snapshotResponses);
425+
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotKVs);
420426
const connectionString = createMockedConnectionString();
421427
const settings = await load(connectionString, {
422428
featureFlagOptions: {

test/load.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,18 +581,22 @@ describe("load", function () {
581581

582582
it("should load key values from snapshot", async () => {
583583
const snapshotName = "Test";
584-
mockAppConfigurationClientGetSnapshot(snapshotName, {compositionType: "key"});
585-
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotName, [[{key: "TestKey", value: "TestValue"}].map(createMockedKeyValue)]);
584+
const snapshotResponses = new Map([
585+
[snapshotName, { compositionType: "key" }]
586+
]);
587+
const snapshotKVs = new Map([
588+
[snapshotName, [[{key: "TestKey", value: "TestValue"}].map(createMockedKeyValue)]]]
589+
);
590+
mockAppConfigurationClientGetSnapshot(snapshotResponses);
591+
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotKVs);
586592
const connectionString = createMockedConnectionString();
587593
const settings = await load(connectionString, {
588594
selectors: [{
589595
snapshotName: snapshotName
590596
}]
591597
});
592598
expect(settings).not.undefined;
593-
expect(settings).not.undefined;
594599
expect(settings.get("TestKey")).eq("TestValue");
595-
restoreMocks();
596600
});
597601
});
598602
/* eslint-enable @typescript-eslint/no-unused-expressions */

test/snapshotReference.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
/* eslint-disable @typescript-eslint/no-unused-expressions */
5+
import * as chai from "chai";
6+
import chaiAsPromised from "chai-as-promised";
7+
chai.use(chaiAsPromised);
8+
const expect = chai.expect;
9+
import { load } from "../src/index.js";
10+
import {
11+
mockAppConfigurationClientListConfigurationSettings,
12+
mockAppConfigurationClientGetSnapshot,
13+
mockAppConfigurationClientListConfigurationSettingsForSnapshot,
14+
restoreMocks,
15+
createMockedConnectionString,
16+
createMockedKeyValue,
17+
createMockedSnapshotReference,
18+
createMockedFeatureFlag,
19+
sleepInMs
20+
} from "./utils/testHelper.js";
21+
import * as uuid from "uuid";
22+
23+
const mockedKVs = [{
24+
key: "TestKey1",
25+
value: "Value1",
26+
}, {
27+
key: "TestKey2",
28+
value: "Value2",
29+
}
30+
].map(createMockedKeyValue);
31+
32+
mockedKVs.push(createMockedSnapshotReference("TestSnapshotRef", "TestSnapshot1"));
33+
34+
// TestSnapshot1
35+
const snapshot1 = [{
36+
key: "TestKey1",
37+
value: "Value1 in snapshot1",
38+
}
39+
].map(createMockedKeyValue);
40+
const testFeatureFlag = createMockedFeatureFlag("TestFeatureFlag");
41+
snapshot1.push(testFeatureFlag);
42+
43+
// TestSnapshot2
44+
const snapshot2 = [{
45+
key: "TestKey1",
46+
value: "Value1 in snapshot2",
47+
}
48+
].map(createMockedKeyValue);
49+
50+
describe("snapshot reference", function () {
51+
52+
beforeEach(() => {
53+
const snapshotResponses = new Map([
54+
["TestSnapshot1", { compositionType: "key" }],
55+
["TestSnapshot2", { compositionType: "key" }]]
56+
);
57+
const snapshotKVs = new Map([
58+
["TestSnapshot1", [snapshot1]],
59+
["TestSnapshot2", [snapshot2]]]
60+
);
61+
mockAppConfigurationClientGetSnapshot(snapshotResponses);
62+
mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotKVs);
63+
mockAppConfigurationClientListConfigurationSettings([mockedKVs]);
64+
});
65+
66+
afterEach(() => {
67+
restoreMocks();
68+
});
69+
70+
it("should resolve snapshot reference", async () => {
71+
const connectionString = createMockedConnectionString();
72+
const settings = await load(connectionString);
73+
expect(settings.get("TestKey1")).eq("Value1 in snapshot1");
74+
75+
// it should ignore feature flags in snapshot
76+
expect(settings.get(testFeatureFlag.key)).to.be.undefined;
77+
expect(settings.get("feature_management")).to.be.undefined;
78+
79+
// it should not load the snapshot reference key
80+
expect(settings.get("TestSnapshotRef")).to.be.undefined;
81+
});
82+
83+
it("should refresh when snapshot reference changes", async () => {
84+
const connectionString = createMockedConnectionString();
85+
const settings = await load(connectionString, {
86+
refreshOptions: {
87+
enabled: true,
88+
refreshIntervalInMs: 2000
89+
}
90+
});
91+
expect(settings.get("TestKey1")).eq("Value1 in snapshot1");
92+
93+
const setting = mockedKVs.find(kv => kv.key === "TestSnapshotRef");
94+
setting!.value = "{\"snapshot_name\":\"TestSnapshot2\"}";
95+
setting!.etag = uuid.v4();
96+
97+
await sleepInMs(2 * 1000 + 1);
98+
99+
await settings.refresh();
100+
101+
expect(settings.get("TestKey1")).eq("Value1 in snapshot2");
102+
});
103+
104+
});

test/utils/testHelper.ts

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

44
import * as sinon from "sinon";
5-
import { AppConfigurationClient, ConfigurationSetting, featureFlagContentType } from "@azure/app-configuration";
5+
import { AppConfigurationClient, ConfigurationSetting, featureFlagContentType, secretReferenceContentType } from "@azure/app-configuration";
66
import { ClientSecretCredential } from "@azure/identity";
77
import { KeyVaultSecret, SecretClient } from "@azure/keyvault-secrets";
88
import * as uuid from "uuid";
@@ -186,29 +186,29 @@ function mockAppConfigurationClientGetConfigurationSetting(kvList: any[], custom
186186
});
187187
}
188188

189-
function mockAppConfigurationClientGetSnapshot(snapshotName: string, mockedResponse: any, customCallback?: (options) => any) {
189+
function mockAppConfigurationClientGetSnapshot(snapshotResponses: Map<string, any>, customCallback?: (options) => any) {
190190
sinon.stub(AppConfigurationClient.prototype, "getSnapshot").callsFake((name, options) => {
191191
if (customCallback) {
192192
customCallback(options);
193193
}
194194

195-
if (name === snapshotName) {
196-
return mockedResponse;
195+
if (snapshotResponses.has(name)) {
196+
return snapshotResponses.get(name);
197197
} else {
198198
throw new RestError("", { statusCode: 404 });
199199
}
200200
});
201201
}
202202

203-
function mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotName: string, pages: ConfigurationSetting[][], customCallback?: (options) => any) {
203+
function mockAppConfigurationClientListConfigurationSettingsForSnapshot(snapshotResponses: Map<string, ConfigurationSetting[][]>, customCallback?: (options) => any) {
204204
sinon.stub(AppConfigurationClient.prototype, "listConfigurationSettingsForSnapshot").callsFake((name, listOptions) => {
205205
if (customCallback) {
206206
customCallback(listOptions);
207207
}
208208

209-
if (name === snapshotName) {
210-
const kvs = _filterKVs(pages.flat(), listOptions);
211-
return getMockedIterator(pages, kvs, listOptions);
209+
if (snapshotResponses.has(name)) {
210+
const kvs = _filterKVs(snapshotResponses.get(name)!.flat(), listOptions);
211+
return getMockedIterator(snapshotResponses.get(name)!, kvs, listOptions);
212212
} else {
213213
throw new RestError("", { statusCode: 404 });
214214
}
@@ -253,7 +253,7 @@ const createMockedKeyVaultReference = (key: string, vaultUri: string): Configura
253253
// https://${vaultName}.vault.azure.net/secrets/${secretName}
254254
value: `{"uri":"${vaultUri}"}`,
255255
key,
256-
contentType: "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8",
256+
contentType: secretReferenceContentType,
257257
lastModified: new Date(),
258258
tags: {},
259259
etag: uuid.v4(),
@@ -297,6 +297,16 @@ const createMockedFeatureFlag = (name: string, flagProps?: any, props?: any) =>
297297
isReadOnly: false
298298
}, props));
299299

300+
const createMockedSnapshotReference = (key: string, snapshotName: string): ConfigurationSetting => ({
301+
value: `{"snapshot_name":"${snapshotName}"}`,
302+
key,
303+
contentType: "application/json; profile=\"https://azconfig.io/mime-profiles/snapshot-ref\"; charset=utf-8",
304+
lastModified: new Date(),
305+
tags: {},
306+
etag: uuid.v4(),
307+
isReadOnly: false,
308+
});
309+
300310
class HttpRequestHeadersPolicy {
301311
headers: any;
302312
name: string;
@@ -329,6 +339,7 @@ export {
329339
createMockedJsonKeyValue,
330340
createMockedKeyValue,
331341
createMockedFeatureFlag,
342+
createMockedSnapshotReference,
332343

333344
sleepInMs,
334345
HttpRequestHeadersPolicy

vitest.browser.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ export default defineConfig({
1010
{ browser: "chromium" },
1111
],
1212
},
13-
include: ["out/esm/test/load.test.js", "out/esm/test/refresh.test.js", "out/esm/test/featureFlag.test.js", "out/esm/test/json.test.js", "out/esm/test/startup.test.js"],
13+
include: ["out/esm/test/load.test.js", "out/esm/test/refresh.test.js", "out/esm/test/featureFlag.test.js", "out/esm/test/json.test.js", "out/esm/test/startup.test.js", "out/esm/test/snapshotReference.test.js"],
1414
testTimeout: 200_000,
1515
hookTimeout: 200_000,
1616
reporters: "default",
1717
globals: true,
1818
// Provide Mocha-style hooks as globals
1919
setupFiles: ["./vitest.setup.mjs"],
2020
},
21-
});
21+
});

0 commit comments

Comments
 (0)