diff --git a/src/featureManager.ts b/src/featureManager.ts index 99b7700..d113f74 100644 --- a/src/featureManager.ts +++ b/src/featureManager.ts @@ -36,8 +36,11 @@ export class FeatureManager { return false; } - if (featureFlag.enabled === false) { - // If the feature is explicitly disabled, then it is disabled. + // Ensure that the feature flag is in the correct format. Feature providers should validate the feature flags, but we do it here as a safeguard. + validateFeatureFlagFormat(featureFlag); + + if (featureFlag.enabled !== true) { + // If the feature is not explicitly enabled, then it is disabled by default. return false; } @@ -77,3 +80,9 @@ export class FeatureManager { interface FeatureManagerOptions { customFilters?: IFeatureFilter[]; } + +function validateFeatureFlagFormat(featureFlag: any): void { + if (featureFlag.enabled !== undefined && typeof featureFlag.enabled !== "boolean") { + throw new Error(`Feature flag ${featureFlag.id} has an invalid 'enabled' value.`); + } +} diff --git a/test/noFilters.test.ts b/test/noFilters.test.ts new file mode 100644 index 0000000..a303748 --- /dev/null +++ b/test/noFilters.test.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as chai from "chai"; +import * as chaiAsPromised from "chai-as-promised"; +chai.use(chaiAsPromised); +const expect = chai.expect; + +import { FeatureManager, ConfigurationObjectFeatureFlagProvider } from "./exportedApi"; + +const featureFlagsDataObject = { + "feature_management": { + "feature_flags": [ + { + "id": "BooleanTrue", + "description": "A feature flag with no Filters, that returns true.", + "enabled": true, + "conditions": { + "client_filters": [] + } + }, + { + "id": "BooleanFalse", + "description": "A feature flag with no Filters, that returns false.", + "enabled": false, + "conditions": { + "client_filters": [] + } + }, + { + "id": "InvalidEnabled", + "description": "A feature flag with an invalid 'enabled' value, that throws an exception.", + "enabled": "invalid", + "conditions": { + "client_filters": [] + } + }, + { + "id": "Minimal", + "enabled": true + }, + { + "id": "NoEnabled" + }, + { + "id": "EmptyConditions", + "description": "A feature flag with no values in conditions, that returns true.", + "enabled": true, + "conditions": { + } + } + ] + } +}; + +describe("feature flags with no filters", () => { + it("should validate feature flags without filters", () => { + const provider = new ConfigurationObjectFeatureFlagProvider(featureFlagsDataObject); + const featureManager = new FeatureManager(provider); + + return Promise.all([ + expect(featureManager.isEnabled("BooleanTrue")).eventually.eq(true), + expect(featureManager.isEnabled("BooleanFalse")).eventually.eq(false), + expect(featureManager.isEnabled("InvalidEnabled")).eventually.rejectedWith("Feature flag InvalidEnabled has an invalid 'enabled' value."), + expect(featureManager.isEnabled("Minimal")).eventually.eq(true), + expect(featureManager.isEnabled("NoEnabled")).eventually.eq(false), + expect(featureManager.isEnabled("EmptyConditions")).eventually.eq(true) + ]); + }); +});