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(); _mockClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(); _featureFlagWrapper = new FeatureFlagWrapper( _mockFeatureFlagServiceClient, _mockClaimsPrincipalAccessor); } #region Test Case Sources public static IEnumerable FeatureFlagTestCases() { foreach (FeatureFlag featureFlag in Enum.GetValues()) { var expectedKey = FeatureFlagLookup.Values[featureFlag]; yield return new TestCaseData(featureFlag, expectedKey) .SetName("IsFeatureFlagOn_{0}_ReturnsTrue"); } } public static IEnumerable FeatureFlagDisabledTestCases() { foreach (FeatureFlag featureFlag in Enum.GetValues().Take(3)) { yield return new TestCaseData(featureFlag) .SetName("IsFeatureFlagOn_{0}_ReturnsFalse"); } } public static IEnumerable 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(), It.IsAny())) .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(), It.IsAny()), 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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny()), 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(), It.IsAny())) .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(), It.IsAny()), 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(), It.IsAny())) .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(), It.IsAny()), Times.Once); } [Test] public async Task IsChargeCodeBetaEnabled_WithClientId_UsesProvidedClientId() { // Arrange Mock.Get(_mockFeatureFlagServiceClient) .Setup(x => x.IsEnabledAsync("chargecodebeta", _alternateClientId, false, It.IsAny(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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(), It.IsAny())) .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( 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(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(expectedException); // Act & Assert var ex = Assert.ThrowsAsync( 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(), It.IsAny())) .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(), It.IsAny()), 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(), It.IsAny())) .ReturnsAsync(true); Mock.Get(_mockFeatureFlagServiceClient) .Setup(x => x.IsEnabledAsync("cibenchmarkingenabled", _testClientId, false, It.IsAny(), It.IsAny())) .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(), It.IsAny(), It.IsAny())) .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 } }