Files

644 lines
24 KiB
C#

using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.Utilities;
using Strata.CoreLib.Claims;
using Strata.FeatureFlags.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
{
[TestFixture, Category("Unit")]
public class FeatureFlagWrapperTests
{
private IFeatureFlagServiceClient _mockFeatureFlagServiceClient;
private IClaimsPrincipalAccessor _mockClaimsPrincipalAccessor;
private FeatureFlagWrapper _featureFlagWrapper;
private readonly Guid _testClientId = Guid.Parse(TestUtilities.DbGuid);
private readonly Guid _alternateClientId = Guid.NewGuid();
[SetUp]
public void SetUp()
{
_mockFeatureFlagServiceClient = Mock.Of<IFeatureFlagServiceClient>();
_mockClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
_featureFlagWrapper = new FeatureFlagWrapper(
_mockFeatureFlagServiceClient,
_mockClaimsPrincipalAccessor);
}
#region Test Case Sources
public static IEnumerable<TestCaseData> FeatureFlagTestCases()
{
foreach (FeatureFlag featureFlag in Enum.GetValues<FeatureFlag>())
{
var expectedKey = FeatureFlagLookup.Values[featureFlag];
yield return new TestCaseData(featureFlag, expectedKey)
.SetName("IsFeatureFlagOn_{0}_ReturnsTrue");
}
}
public static IEnumerable<TestCaseData> FeatureFlagDisabledTestCases()
{
foreach (FeatureFlag featureFlag in Enum.GetValues<FeatureFlag>().Take(3))
{
yield return new TestCaseData(featureFlag)
.SetName("IsFeatureFlagOn_{0}_ReturnsFalse");
}
}
public static IEnumerable<TestCaseData> FeatureFlagConsistencyTestCases()
{
yield return new TestCaseData(true, false)
.SetName("FeatureFlagConsistency_EnabledFlag_DefaultFalse");
yield return new TestCaseData(true, true)
.SetName("FeatureFlagConsistency_EnabledFlag_DefaultTrue");
yield return new TestCaseData(false, false)
.SetName("FeatureFlagConsistency_DisabledFlag_DefaultFalse");
yield return new TestCaseData(false, true)
.SetName("FeatureFlagConsistency_DisabledFlag_DefaultTrue");
}
#endregion
#region IsFeatureFlagOn Tests
[Test]
[TestCaseSource(nameof(FeatureFlagTestCases))]
public async Task IsFeatureFlagOn_WhenFeatureFlagEnabled_ReturnsTrue(FeatureFlag featureFlag, string expectedKey)
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync(expectedKey, _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsFeatureFlagOn(featureFlag, _testClientId);
// Assert
Assert.That(result, Is.True);
Mock.Get(_mockFeatureFlagServiceClient).Verify(
x => x.IsEnabledAsync(expectedKey, _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()),
Times.Once);
}
[Test]
[TestCaseSource(nameof(FeatureFlagDisabledTestCases))]
public async Task IsFeatureFlagOn_WhenFeatureFlagDisabled_ReturnsFalse(FeatureFlag featureFlag)
{
// Arrange
var expectedKey = FeatureFlagLookup.Values[featureFlag];
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync(expectedKey, _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsFeatureFlagOn(featureFlag, _testClientId);
// Assert
Assert.That(result, Is.False);
}
[Test]
public async Task IsFeatureFlagOn_WithCustomDefaultValue_UsesProvidedDefault()
{
// Arrange
const bool customDefaultValue = true;
var featureFlag = FeatureFlag.ChargeCodeBeta;
var expectedKey = FeatureFlagLookup.Values[featureFlag];
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync(expectedKey, _testClientId, customDefaultValue, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(customDefaultValue);
// Act
var result = await _featureFlagWrapper.IsFeatureFlagOn(featureFlag, _testClientId, customDefaultValue);
// Assert
Assert.That(result, Is.EqualTo(customDefaultValue));
Mock.Get(_mockFeatureFlagServiceClient).Verify(
x => x.IsEnabledAsync(expectedKey, _testClientId, customDefaultValue, It.IsAny<bool>(), It.IsAny<CancellationToken>()),
Times.Once);
}
[Test]
public async Task IsFeatureFlagOn_WithDifferentClientIds_CallsServiceWithCorrectClientId()
{
// Arrange
var featureFlag = FeatureFlag.ChargeCodeBeta;
var expectedKey = FeatureFlagLookup.Values[featureFlag];
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync(expectedKey, _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsFeatureFlagOn(featureFlag, _alternateClientId);
// Assert
Assert.That(result, Is.True);
Mock.Get(_mockFeatureFlagServiceClient).Verify(
x => x.IsEnabledAsync(expectedKey, _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()),
Times.Once);
}
#endregion
#region ChargeCodeBeta Tests
[Test]
public async Task IsChargeCodeBetaEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsChargeCodeBetaEnabled();
// Assert
Assert.That(result, Is.True);
Mock.Get(_mockFeatureFlagServiceClient).Verify(
x => x.IsEnabledAsync("chargecodebeta", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()),
Times.Once);
}
[Test]
public async Task IsChargeCodeBetaEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsChargeCodeBetaEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
[Test]
public async Task IsChargeCodeBetaEnabled_WithDefaultValue_UsesCustomDefault()
{
// Arrange
const bool customDefault = true;
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", _testClientId, customDefault, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(customDefault);
// Act
var result = await _featureFlagWrapper.IsChargeCodeBetaEnabled(customDefault);
// Assert
Assert.That(result, Is.EqualTo(customDefault));
}
#endregion
#region CiBenchmarkingEnabled Tests
[Test]
public async Task IsCiBenchmarkingEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("cibenchmarkingenabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsCiBenchmarkingEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsCiBenchmarkingEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("cibenchmarkingenabled", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsCiBenchmarkingEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region CiExplorationPopulationStrategicOpportunityWizard Tests
[Test]
public async Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciexplorationpopulationstrategicopportunitywizard", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsCiExplorationPopulationStrategicOpportunityWizardEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciexplorationpopulationstrategicopportunitywizard", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsCiExplorationPopulationStrategicOpportunityWizardEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region CiFlexibleExplorationEnabled Tests
[Test]
public async Task IsCiFlexibleExplorationEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciflexibleexplorationenabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsCiFlexibleExplorationEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsCiFlexibleExplorationEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciflexibleexplorationenabled", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsCiFlexibleExplorationEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region CiFlexibleExplorationPage3Enabled Tests
[Test]
public async Task IsCiFlexibleExplorationPage3Enabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciflexibleexplorationpage3enabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsCiFlexibleExplorationPage3Enabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsCiFlexibleExplorationPage3Enabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciflexibleexplorationpage3enabled", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsCiFlexibleExplorationPage3Enabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region CiOpportunityWorkbooks Tests
[Test]
public async Task IsCiOpportunityWorkbooksEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciopportunityworkbooks", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsCiOpportunityWorkbooksEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsCiOpportunityWorkbooksEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("ciopportunityworkbooks", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsCiOpportunityWorkbooksEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region DecisionSupportQueries Tests
[Test]
public async Task IsDecisionSupportQueriesEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("is-decision-support-queries-enabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsDecisionSupportQueriesEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsDecisionSupportQueriesEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("is-decision-support-queries-enabled", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsDecisionSupportQueriesEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region EnableEncounterOpportunityInTempo Tests
[Test]
public async Task IsEnableEncounterOpportunityInTempoEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("enableencounteropportunityintempo", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsEnableEncounterOpportunityInTempoEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsEnableEncounterOpportunityInTempoEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("enableencounteropportunityintempo", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsEnableEncounterOpportunityInTempoEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region EnablePayrollOpportunityInTempo Tests
[Test]
public async Task IsEnablePayrollOpportunityInTempoEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("enablepayrollopportunityintempo", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsEnablePayrollOpportunityInTempoEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsEnablePayrollOpportunityInTempoEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("enablepayrollopportunityintempo", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsEnablePayrollOpportunityInTempoEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region LoadExplorationPopulationDataFromPostgres Tests
[Test]
public async Task IsLoadExplorationPopulationDataFromPostgresEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("loadexplorationpopulationdatafrompostgres", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsLoadExplorationPopulationDataFromPostgresEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsLoadExplorationPopulationDataFromPostgresEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("loadexplorationpopulationdatafrompostgres", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsLoadExplorationPopulationDataFromPostgresEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region StrategicOpportunityEnabled Tests
[Test]
public async Task IsStrategicOpportunityEnabled_WithoutClientId_UsesCurrentUserDatabaseGuid()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("strategic-opportunity-enabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _featureFlagWrapper.IsStrategicOpportunityEnabled();
// Assert
Assert.That(result, Is.True);
}
[Test]
public async Task IsStrategicOpportunityEnabled_WithClientId_UsesProvidedClientId()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("strategic-opportunity-enabled", _alternateClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsStrategicOpportunityEnabled(_alternateClientId);
// Assert
Assert.That(result, Is.False);
}
#endregion
#region Exception Handling Tests
[Test]
public void IsFeatureFlagOn_WithInvalidFeatureFlag_ThrowsArgumentException()
{
// Arrange
const FeatureFlag invalidFeatureFlag = (FeatureFlag)999;
// Act & Assert
Assert.ThrowsAsync<KeyNotFoundException>(
async () => await _featureFlagWrapper.IsFeatureFlagOn(invalidFeatureFlag, _testClientId));
}
#endregion
#region Edge Case Tests
[Test]
public async Task AllFeatureFlagMethods_WithServiceException_PropagatesException()
{
// Arrange
var expectedException = new InvalidOperationException("Service unavailable");
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync(It.IsAny<string>(), It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(expectedException);
// Act & Assert
var ex = Assert.ThrowsAsync<InvalidOperationException>(
async () => await _featureFlagWrapper.IsChargeCodeBetaEnabled());
Assert.That(ex.Message, Is.EqualTo("Service unavailable"));
}
[Test]
public async Task AllFeatureFlagMethods_WithEmptyGuid_HandlesGracefully()
{
// Arrange
var emptyGuid = Guid.Empty;
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", emptyGuid, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var result = await _featureFlagWrapper.IsChargeCodeBetaEnabled(emptyGuid);
// Assert
Assert.That(result, Is.False);
Mock.Get(_mockFeatureFlagServiceClient).Verify(
x => x.IsEnabledAsync("chargecodebeta", emptyGuid, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()),
Times.Once);
}
#endregion
#region Integration Tests with Realistic Scenarios
[Test]
public async Task MultipleConcurrentFeatureFlagCalls_HandlesCorrectly()
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("cibenchmarkingenabled", _testClientId, false, It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
// Act
var task1 = _featureFlagWrapper.IsChargeCodeBetaEnabled();
var task2 = _featureFlagWrapper.IsCiBenchmarkingEnabled();
await Task.WhenAll(task1, task2);
// Assert
Assert.That(task1.Result, Is.True);
Assert.That(task2.Result, Is.False);
}
[Test]
[TestCaseSource(nameof(FeatureFlagConsistencyTestCases))]
public async Task FeatureFlagConsistency_BetweenOverloads_ReturnsExpectedResults(bool enabledFlag, bool customDefault)
{
// Arrange
Mock.Get(_mockFeatureFlagServiceClient)
.Setup(x => x.IsEnabledAsync("chargecodebeta", _testClientId, It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(enabledFlag);
// Act
var resultWithoutClientId = await _featureFlagWrapper.IsChargeCodeBetaEnabled(customDefault);
var resultWithClientId = await _featureFlagWrapper.IsChargeCodeBetaEnabled(_testClientId, customDefault);
// Assert
Assert.That(resultWithoutClientId, Is.EqualTo(enabledFlag));
Assert.That(resultWithClientId, Is.EqualTo(enabledFlag));
Assert.That(resultWithoutClientId, Is.EqualTo(resultWithClientId));
}
#endregion
}
}