Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ jobs:
build-mode: none # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too.
- language: python
build-mode: none
- language: javascript-typescript
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,10 @@ venv/
.project
.settings
mvnw*
target/
target/

package-lock.json

# JavaScript bundler folder
out/
*.tgz
6 changes: 3 additions & 3 deletions Samples/BasicVariant.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"id": "Variant_Override_True",
"description": "",
"enabled": "true",
"enabled": true,
"conditions": {
"client_filters": []
},
Expand All @@ -23,7 +23,7 @@
{
"id": "Variant_Override_False",
"description": "",
"enabled": "false",
"enabled": false,
"conditions": {
"client_filters": []
},
Expand All @@ -42,7 +42,7 @@
{
"id": "TestVariants",
"description": "",
"enabled": "true",
"enabled": true,
"allocation": {
"user": [
{
Expand Down
8 changes: 4 additions & 4 deletions Samples/VariantAssignment.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"id": "UserAssignedVariant",
"description": "",
"enabled": "true",
"enabled": true,
"allocation": {
"user": [
{
Expand Down Expand Up @@ -36,7 +36,7 @@
{
"id": "GroupAssignedVariant",
"description": "",
"enabled": "true",
"enabled": true,
"allocation": {
"group": [
{
Expand Down Expand Up @@ -67,7 +67,7 @@
{
"id": "AllocationAssignedVariant",
"description": "",
"enabled": "true",
"enabled": true,
"allocation": {
"percentile": [
{
Expand Down Expand Up @@ -96,7 +96,7 @@
{
"id": "ComplexAssignment",
"description": "",
"enabled": "true",
"enabled": true,
"allocation": {
"user": [
{
Expand Down
13 changes: 13 additions & 0 deletions libraryValidations/JavaScript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# JavaScript Feature Management Validation Tests

This directory contains test cases to verify that the correctness of the latest JS Feature Management library against the files in the `Samples` directory.

## Running the test

To run the tests, execute the following command:

```bash
npm install
npm run build
npm run test
```
51 changes: 51 additions & 0 deletions libraryValidations/JavaScript/featureEvaluationValidation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as fs from "node:fs/promises";
import { FeatureManager, ConfigurationObjectFeatureFlagProvider } from "@microsoft/feature-management";
import {FILE_PATH, SAMPLE_JSON_KEY, TESTS_JSON_KEY, validateFeatureEvaluation, FeatureFlagTest } from "./utils.js";

async function runTest(testName: string) {
const config = JSON.parse(await fs.readFile(FILE_PATH + testName + SAMPLE_JSON_KEY, "utf8"));
const testcases: FeatureFlagTest[] = JSON.parse(await fs.readFile(FILE_PATH + testName + TESTS_JSON_KEY, "utf8"));
const ffProvider = new ConfigurationObjectFeatureFlagProvider(config);
const fm = new FeatureManager(ffProvider);

for (const testcase of testcases){
validateFeatureEvaluation(testcase, fm);
}
}

describe("feature evaluation validation", function () {
it("should pass NoFilters test", async () => {
await runTest("NoFilters");
});

it("should pass RequirementType test", async () => {
await runTest("RequirementType");
});

it("should pass RequirementType test", async () => {
await runTest("RequirementType");
});

it("should pass TimeWindowFilter test", async () => {
await runTest("TimeWindowFilter");
});

it("should pass TargetingFilter test", async () => {
await runTest("TargetingFilter");
});

it("should pass TargetingFilter.modified test", async () => {
await runTest("TargetingFilter.modified");
});

it("should pass BasicVariant test", async () => {
await runTest("BasicVariant");
});

it("should pass VariantAssignment test", async () => {
await runTest("VariantAssignment");
});
});
27 changes: 27 additions & 0 deletions libraryValidations/JavaScript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"type": "module",
"scripts": {
"build": "npm run clean && tsc -p ./tsconfig.json",
"clean": "rimraf out ",
"test": "mocha out/*.test.{js,cjs,mjs} --parallel"
},
"dependencies": {
"@types/chai-as-promised": "^8.0.1",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.7",
"@types/sinon": "^17.0.1",
"chai": "^5.1.2",
"chai-as-promised": "^8.0.0",
"mocha": "^10.2.0",
"rimraf": "^5.0.5",
"sinon": "^15.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"@microsoft/feature-management": "2.0.0-preview.3",
"@microsoft/feature-management-applicationinsights-browser": "2.0.0-preview.3",
"@microsoft/feature-management-applicationinsights-node": "2.0.0-preview.3",
"@azure/app-configuration-provider": "2.0.0-preview.1",
"@microsoft/applicationinsights-web": "^3.3.4",
"applicationinsights": "^2.9.6"
}
}
106 changes: 106 additions & 0 deletions libraryValidations/JavaScript/telemetryWithProviderValidation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as sinon from "sinon";
import * as fs from "node:fs/promises";
import { load } from "@azure/app-configuration-provider";
import { FeatureManager, ConfigurationMapFeatureFlagProvider } from "@microsoft/feature-management";
import { createTelemetryPublisher as createNodeTelemetryPublisher } from "@microsoft/feature-management-applicationinsights-node";
import { createTelemetryPublisher as createBrowserTelemetryPublisher } from "@microsoft/feature-management-applicationinsights-browser";
import {FILE_PATH, TESTS_JSON_KEY, FeatureFlagTest, validateFeatureEvaluation, validateTelemetryWithProvider } from "./utils.js";
import { ApplicationInsights } from "@microsoft/applicationinsights-web";
import applicationInsights from "applicationinsights";

// For telemetry validation
let eventNameToValidate;
let eventPropertiesToValidate;

applicationInsights.setup("DUMMY-CONNECTION-STRING").start();
sinon.stub(applicationInsights.defaultClient, "trackEvent").callsFake((event) => {
eventNameToValidate = event.name;
eventPropertiesToValidate = event.properties;
});

const appInsights = new ApplicationInsights({ config: { connectionString: "DUMMY-CONNECTION-STRING" }});
sinon.stub(appInsights, "trackEvent").callsFake((event, customProperties) => {
eventNameToValidate = event.name;
eventPropertiesToValidate = customProperties;
});

async function runTestWithProviderAndNodePackage(testName: string) {
const connectionString = process.env["APP_CONFIG_VALIDATION_CONNECTION_STRING"];
if (connectionString === undefined) {
console.log("Skipping test as environment variable APP_CONFIG_VALIDATION_CONNECTION_STRING is not set.");
return;
}
const config = await load(
connectionString,
{
featureFlagOptions: {
enabled: true,
selectors: [
{
keyFilter: "*"
}
]
}
});
const testcases: FeatureFlagTest[] = JSON.parse(await fs.readFile(FILE_PATH + testName + TESTS_JSON_KEY, "utf8"));
const ffProvider = new ConfigurationMapFeatureFlagProvider(config);
const fm = new FeatureManager(ffProvider, { onFeatureEvaluated: createNodeTelemetryPublisher(applicationInsights.defaultClient) });
for (const testcase of testcases){
const featureFlagName = testcase.FeatureFlagName;
const context = { userId: testcase.Inputs?.User, groups: testcase.Inputs?.Groups };
await fm.getVariant(featureFlagName, context);
validateTelemetryWithProvider(testcase, connectionString, eventNameToValidate, eventPropertiesToValidate);
}
}

async function runTestWithProviderAndBrowserPackage(testName: string) {
const connectionString = process.env["APP_CONFIG_VALIDATION_CONNECTION_STRING"];
if (connectionString === undefined) {
console.log("Skipping test as environment variable APP_CONFIG_VALIDATION_CONNECTION_STRING is not set.");
return;
}
const config = await load(
connectionString,
{
featureFlagOptions: {
enabled: true,
selectors: [
{
keyFilter: "*"
}
]
}
});
const testcases: FeatureFlagTest[] = JSON.parse(await fs.readFile(FILE_PATH + testName + TESTS_JSON_KEY, "utf8"));
const ffProvider = new ConfigurationMapFeatureFlagProvider(config);
const fm = new FeatureManager(ffProvider, { onFeatureEvaluated: createBrowserTelemetryPublisher(appInsights) });
for (const testcase of testcases){
const featureFlagName = testcase.FeatureFlagName;
const context = { userId: testcase.Inputs?.User, groups: testcase.Inputs?.Groups };
await fm.getVariant(featureFlagName, context);
validateTelemetryWithProvider(testcase, connectionString, eventNameToValidate, eventPropertiesToValidate);
}
}

describe("telemetry with provider and node package", function () {
it("should pass ProviderTelemetry test", async () => {
await runTestWithProviderAndNodePackage("ProviderTelemetry");
});

it("should pass ProviderTelemetryComplete test", async () => {
await runTestWithProviderAndNodePackage("ProviderTelemetryComplete");
});
});

describe("telemetry with provider and browser package", function () {
it("should pass ProviderTelemetry test", async () => {
await runTestWithProviderAndBrowserPackage("ProviderTelemetry");
});

it("should pass ProviderTelemetryComplete test", async () => {
await runTestWithProviderAndBrowserPackage("ProviderTelemetryComplete");
});
});
23 changes: 23 additions & 0 deletions libraryValidations/JavaScript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": [
"DOM",
"WebWorker",
"ESNext"
],
"skipDefaultLibCheck": true,
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2022",
"strictNullChecks": true,
"strictFunctionTypes": true,
"sourceMap": true,
"inlineSources": true,
"esModuleInterop": true,
"outDir": "./out"
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}
Loading
Loading