Template
chore: deploy initial code base
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
using ClosedXML.Excel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.Packaging;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal static class ExcelDocsComparer
|
||||
{
|
||||
public static bool Compare(string left, string right, out string message)
|
||||
{
|
||||
using (FileStream leftStream = File.OpenRead(left))
|
||||
using (FileStream rightStream = File.OpenRead(right))
|
||||
{
|
||||
return Compare(leftStream, rightStream, out message);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Compare(this Stream result, Stream expected, out string message)
|
||||
{
|
||||
using (Package leftPackage = Package.Open(result, FileMode.Open, FileAccess.Read))
|
||||
using (Package rightPackage = Package.Open(expected, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
return PackageHelper.Compare(leftPackage, rightPackage, false, ExcludeMethod, out message);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Compare(this IXLWorkbook result, IXLWorkbook expected, out string message)
|
||||
{
|
||||
using (var resultStream = new MemoryStream())
|
||||
using (var wbStream = new MemoryStream())
|
||||
{
|
||||
expected.SaveAs(wbStream);
|
||||
result.SaveAs(resultStream);
|
||||
return wbStream.Compare(resultStream, out message);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ExcludeMethod(Uri uri)
|
||||
{
|
||||
//Exclude service data
|
||||
if (uri.OriginalString.EndsWith(".rels") ||
|
||||
uri.OriginalString.EndsWith(".psmdcp"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Dictionary<string, DataTable> GetDataTables(this IXLWorkbook workbook)
|
||||
{
|
||||
var dataTables = new Dictionary<string, DataTable>();
|
||||
foreach (var ws in workbook.Worksheets)
|
||||
{
|
||||
var name = ws.Name;
|
||||
DataTable dt = new DataTable();
|
||||
foreach (var cell in ws.RowsUsed().First().CellsUsed())
|
||||
{
|
||||
var columnName = "_" + cell.Value.ToString().ToAlphaNumericOnly();
|
||||
dt.Columns.Add(columnName);
|
||||
}
|
||||
foreach (var row in ws.RowsUsed().Where(x => x.RowNumber() > 1))
|
||||
{
|
||||
DataRow temprow = dt.NewRow();
|
||||
|
||||
for (int i = 0; i < dt.Columns.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cellValue = row.Cells().ElementAt(i).Value.ToString();
|
||||
temprow[i] = cellValue.TrimWhiteSpaces().RemoveLineBreaks();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Empty Cell
|
||||
}
|
||||
}
|
||||
|
||||
dt.Rows.Add(temprow);
|
||||
}
|
||||
dataTables.Add(name, dt);
|
||||
}
|
||||
return dataTables;
|
||||
}
|
||||
private static string ToAlphaNumericOnly(this string input)
|
||||
{
|
||||
Regex rgx = new Regex("[^a-zA-Z0-9]");
|
||||
return rgx.Replace(input, "");
|
||||
}
|
||||
|
||||
public static string TrimWhiteSpaces(this string text)
|
||||
{
|
||||
text = text.TrimStart(' ');
|
||||
text = text.TrimEnd(' ');
|
||||
text = text.Trim();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string RemoveLineBreaks(this string text)
|
||||
{
|
||||
string replaceWith = "";
|
||||
string removedBreaks = text.Replace("\r\n", replaceWith).Replace("\n", replaceWith).Replace("\r", replaceWith);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+644
@@ -0,0 +1,644 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
public interface IXLExample
|
||||
{
|
||||
void Create(string filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.Packaging;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class PackageHelper
|
||||
{
|
||||
public static void WriteXmlPart(Package package, Uri uri, object content, XmlSerializer serializer)
|
||||
{
|
||||
if (package.PartExists(uri))
|
||||
{
|
||||
package.DeletePart(uri);
|
||||
}
|
||||
PackagePart part = package.CreatePart(uri, MediaTypeNames.Text.Xml, CompressionOption.Fast);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
serializer.Serialize(stream, content);
|
||||
}
|
||||
}
|
||||
|
||||
public static object ReadXmlPart(Package package, Uri uri, XmlSerializer serializer)
|
||||
{
|
||||
if (!package.PartExists(uri))
|
||||
{
|
||||
throw new ApplicationException($"Package part '{uri.OriginalString}' doesn't exists!");
|
||||
}
|
||||
PackagePart part = package.GetPart(uri);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
return serializer.Deserialize(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteBinaryPart(Package package, Uri uri, Stream content)
|
||||
{
|
||||
if (package.PartExists(uri))
|
||||
{
|
||||
package.DeletePart(uri);
|
||||
}
|
||||
PackagePart part = package.CreatePart(uri, MediaTypeNames.Application.Octet, CompressionOption.Fast);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
StreamHelper.StreamToStreamAppend(content, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns part's stream
|
||||
/// </summary>
|
||||
/// <param name="package"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public static Stream ReadBinaryPart(Package package, Uri uri)
|
||||
{
|
||||
if (!package.PartExists(uri))
|
||||
{
|
||||
throw new ApplicationException("Package part doesn't exists!");
|
||||
}
|
||||
PackagePart part = package.GetPart(uri);
|
||||
return part.GetStream();
|
||||
}
|
||||
|
||||
public static void CopyPart(Uri uri, Package source, Package dest)
|
||||
{
|
||||
CopyPart(uri, source, dest, true);
|
||||
}
|
||||
|
||||
public static void CopyPart(Uri uri, Package source, Package dest, bool overwrite)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(uri, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
if (ReferenceEquals(source, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
if (ReferenceEquals(dest, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dest));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (dest.PartExists(uri))
|
||||
{
|
||||
if (!overwrite)
|
||||
{
|
||||
throw new ArgumentException("Specified part already exists", nameof(uri));
|
||||
}
|
||||
dest.DeletePart(uri);
|
||||
}
|
||||
|
||||
PackagePart sourcePart = source.GetPart(uri);
|
||||
PackagePart destPart = dest.CreatePart(uri, sourcePart.ContentType, sourcePart.CompressionOption);
|
||||
|
||||
using (Stream sourceStream = sourcePart.GetStream())
|
||||
{
|
||||
using (Stream destStream = destPart.GetStream())
|
||||
{
|
||||
StreamHelper.StreamToStreamAppend(sourceStream, destStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void WritePart<T>(Package package, PackagePartDescriptor descriptor, T content,
|
||||
Action<Stream, T> serializeAction)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(package, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
if (ReferenceEquals(descriptor, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
if (ReferenceEquals(serializeAction, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serializeAction));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (package.PartExists(descriptor.Uri))
|
||||
{
|
||||
package.DeletePart(descriptor.Uri);
|
||||
}
|
||||
PackagePart part = package.CreatePart(descriptor.Uri, descriptor.ContentType, descriptor.CompressOption);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
serializeAction(stream, content);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WritePart(Package package, PackagePartDescriptor descriptor, Action<Stream> serializeAction)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(package, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
if (ReferenceEquals(descriptor, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(descriptor));
|
||||
}
|
||||
if (ReferenceEquals(serializeAction, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serializeAction));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (package.PartExists(descriptor.Uri))
|
||||
{
|
||||
package.DeletePart(descriptor.Uri);
|
||||
}
|
||||
PackagePart part = package.CreatePart(descriptor.Uri, descriptor.ContentType, descriptor.CompressOption);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
serializeAction(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static T ReadPart<T>(Package package, Uri uri, Func<Stream, T> deserializeFunc)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(package, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
if (ReferenceEquals(uri, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
if (ReferenceEquals(deserializeFunc, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deserializeFunc));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (!package.PartExists(uri))
|
||||
{
|
||||
throw new ApplicationException($"Package part '{uri.OriginalString}' doesn't exists!");
|
||||
}
|
||||
PackagePart part = package.GetPart(uri);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
return deserializeFunc(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReadPart(Package package, Uri uri, Action<Stream> deserializeAction)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(package, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
if (ReferenceEquals(uri, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
if (ReferenceEquals(deserializeAction, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deserializeAction));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (!package.PartExists(uri))
|
||||
{
|
||||
throw new ApplicationException($"Package part '{uri.OriginalString}' doesn't exists!");
|
||||
}
|
||||
PackagePart part = package.GetPart(uri);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
deserializeAction(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryReadPart(Package package, Uri uri, Action<Stream> deserializeAction)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(package, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
if (ReferenceEquals(uri, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
if (ReferenceEquals(deserializeAction, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deserializeAction));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
if (!package.PartExists(uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
PackagePart part = package.GetPart(uri);
|
||||
using (Stream stream = part.GetStream())
|
||||
{
|
||||
deserializeAction(stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare to packages by parts like streams
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="compareToFirstDifference"></param>
|
||||
/// <param name="excludeMethod"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Compare(Package left, Package right, bool compareToFirstDifference, out string message)
|
||||
{
|
||||
return Compare(left, right, compareToFirstDifference, null, out message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare to packages by parts like streams
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="compareToFirstDifference"></param>
|
||||
/// <param name="excludeMethod"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Compare(Package left, Package right, bool compareToFirstDifference,
|
||||
Func<Uri, bool> excludeMethod, out string message)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (left == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(left));
|
||||
}
|
||||
if (right == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(right));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
excludeMethod = excludeMethod ?? (uri => false);
|
||||
PackagePartCollection leftParts = left.GetParts();
|
||||
PackagePartCollection rightParts = right.GetParts();
|
||||
|
||||
var pairs = new Dictionary<Uri, PartPair>();
|
||||
foreach (PackagePart part in leftParts)
|
||||
{
|
||||
if (excludeMethod(part.Uri))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pairs.Add(part.Uri, new PartPair(part.Uri, CompareStatus.OnlyOnLeft));
|
||||
}
|
||||
foreach (PackagePart part in rightParts)
|
||||
{
|
||||
if (excludeMethod(part.Uri))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (pairs.TryGetValue(part.Uri, out PartPair pair))
|
||||
{
|
||||
pair.Status = CompareStatus.Equal;
|
||||
}
|
||||
else
|
||||
{
|
||||
pairs.Add(part.Uri, new PartPair(part.Uri, CompareStatus.OnlyOnRight));
|
||||
}
|
||||
}
|
||||
|
||||
if (compareToFirstDifference && pairs.Any(pair => pair.Value.Status != CompareStatus.Equal))
|
||||
{
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
foreach (PartPair pair in pairs.Values)
|
||||
{
|
||||
if (pair.Status != CompareStatus.Equal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var leftPart = left.GetPart(pair.Uri);
|
||||
var rightPart = right.GetPart(pair.Uri);
|
||||
using (Stream leftPackagePartStream = leftPart.GetStream(FileMode.Open, FileAccess.Read))
|
||||
using (Stream rightPackagePartStream = rightPart.GetStream(FileMode.Open, FileAccess.Read))
|
||||
using (var leftMemoryStream = new MemoryStream())
|
||||
using (var rightMemoryStream = new MemoryStream())
|
||||
{
|
||||
leftPackagePartStream.CopyTo(leftMemoryStream);
|
||||
rightPackagePartStream.CopyTo(rightMemoryStream);
|
||||
|
||||
leftMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
rightMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
bool stripColumnWidthsFromSheet = TestHelper.StripColumnWidths &&
|
||||
leftPart.ContentType == @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" &&
|
||||
rightPart.ContentType == @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
||||
|
||||
var tuple1 = new Tuple<Uri, Stream>(pair.Uri, leftMemoryStream);
|
||||
var tuple2 = new Tuple<Uri, Stream>(pair.Uri, rightMemoryStream);
|
||||
|
||||
if (!StreamHelper.Compare(tuple1, tuple2, stripColumnWidthsFromSheet))
|
||||
{
|
||||
pair.Status = CompareStatus.NonEqual;
|
||||
if (compareToFirstDifference)
|
||||
{
|
||||
goto EXIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXIT:
|
||||
List<PartPair> sortedPairs = pairs.Values.ToList();
|
||||
sortedPairs.Sort((one, other) => one.Uri.OriginalString.CompareTo(other.Uri.OriginalString));
|
||||
var sbuilder = new StringBuilder();
|
||||
foreach (PartPair pair in sortedPairs)
|
||||
{
|
||||
if (pair.Status == CompareStatus.Equal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sbuilder.AppendFormat("{0} :{1}", pair.Uri, pair.Status);
|
||||
sbuilder.AppendLine();
|
||||
}
|
||||
message = sbuilder.ToString();
|
||||
return message.Length == 0;
|
||||
}
|
||||
|
||||
#region Nested type: PackagePartDescriptor
|
||||
|
||||
public sealed class PackagePartDescriptor
|
||||
{
|
||||
#region Private fields
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly CompressionOption _compressOption;
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly string _contentType;
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly Uri _uri;
|
||||
|
||||
#endregion Private fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
/// <param name=nameof(uri)>Part uri</param>
|
||||
/// <param name="contentType">Content type from <see cref="MediaTypeNames" /></param>
|
||||
/// <param name="compressOption"></param>
|
||||
public PackagePartDescriptor(Uri uri, string contentType, CompressionOption compressOption)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (ReferenceEquals(uri, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentType));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
_uri = uri;
|
||||
_contentType = contentType;
|
||||
_compressOption = compressOption;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Public properties
|
||||
|
||||
public Uri Uri
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
get => _uri;
|
||||
}
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
get => _contentType;
|
||||
}
|
||||
|
||||
public CompressionOption CompressOption
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
get { return _compressOption; }
|
||||
}
|
||||
|
||||
#endregion Public properties
|
||||
|
||||
#region Public methods
|
||||
|
||||
public override string ToString() => $"Uri:{_uri} ContentType: {_contentType}, Compression: {_compressOption}";
|
||||
|
||||
#endregion Public methods
|
||||
}
|
||||
|
||||
#endregion Nested type: PackagePartDescriptor
|
||||
|
||||
#region Nested type: CompareStatus
|
||||
|
||||
private enum CompareStatus
|
||||
{
|
||||
OnlyOnLeft,
|
||||
OnlyOnRight,
|
||||
Equal,
|
||||
NonEqual
|
||||
}
|
||||
|
||||
#endregion Nested type: CompareStatus
|
||||
|
||||
#region Nested type: PartPair
|
||||
|
||||
private sealed class PartPair
|
||||
{
|
||||
#region Private fields
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly Uri _uri;
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private CompareStatus _status;
|
||||
|
||||
#endregion Private fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
public PartPair(Uri uri, CompareStatus status)
|
||||
{
|
||||
_uri = uri;
|
||||
_status = status;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Public properties
|
||||
|
||||
public Uri Uri
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
get { return _uri; }
|
||||
}
|
||||
|
||||
public CompareStatus Status
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
get { return _status; }
|
||||
[DebuggerStepThrough]
|
||||
set { _status = value; }
|
||||
}
|
||||
|
||||
#endregion Public properties
|
||||
}
|
||||
|
||||
#endregion Nested type: PartPair
|
||||
|
||||
//--
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for ResourceFileExtractor.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public sealed class ResourceFileExtractor
|
||||
{
|
||||
#region Static
|
||||
|
||||
#region Private fields
|
||||
|
||||
private static readonly IDictionary<string, ResourceFileExtractor> extractors = new ConcurrentDictionary<string, ResourceFileExtractor>();
|
||||
|
||||
#endregion Private fields
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>Instance of resource extractor for executing assembly </summary>
|
||||
public static ResourceFileExtractor Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
var _assembly = Assembly.GetCallingAssembly();
|
||||
var _key = _assembly.GetName().FullName;
|
||||
if (extractors.TryGetValue(_key, out var extractor) ||
|
||||
extractors.TryGetValue(_key, out extractor)) return extractor;
|
||||
|
||||
extractor = new ResourceFileExtractor(_assembly, true, null);
|
||||
extractors.Add(_key, extractor);
|
||||
|
||||
return extractor;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public properties
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Private fields
|
||||
|
||||
//private readonly Assembly m_assembly;
|
||||
private readonly ResourceFileExtractor m_baseExtractor;
|
||||
|
||||
//private bool m_isStatic;
|
||||
//private string ResourceFilePath { get; }
|
||||
|
||||
#endregion Private fields
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create instance
|
||||
/// </summary>
|
||||
/// <param name="resourceFilePath"><c>ResourceFilePath</c> in assembly. Example: .Properties.Scripts.</param>
|
||||
/// <param name="baseExtractor"></param>
|
||||
public ResourceFileExtractor(string resourceFilePath, ResourceFileExtractor baseExtractor)
|
||||
: this(Assembly.GetCallingAssembly(), baseExtractor)
|
||||
{
|
||||
ResourceFilePath = resourceFilePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create instance
|
||||
/// </summary>
|
||||
/// <param name="baseExtractor"></param>
|
||||
public ResourceFileExtractor(ResourceFileExtractor baseExtractor)
|
||||
: this(Assembly.GetCallingAssembly(), baseExtractor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create instance
|
||||
/// </summary>
|
||||
/// <param name="resourcePath"><c>ResourceFilePath</c> in assembly. Example: .Properties.Scripts.</param>
|
||||
public ResourceFileExtractor(string resourcePath)
|
||||
: this(Assembly.GetCallingAssembly(), resourcePath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="resourcePath"></param>
|
||||
public ResourceFileExtractor(Assembly assembly, string resourcePath)
|
||||
: this(assembly ?? Assembly.GetCallingAssembly())
|
||||
{
|
||||
ResourceFilePath = resourcePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
public ResourceFileExtractor()
|
||||
: this(Assembly.GetCallingAssembly())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
public ResourceFileExtractor(Assembly assembly)
|
||||
: this(assembly ?? Assembly.GetCallingAssembly(), (ResourceFileExtractor)null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="baseExtractor"></param>
|
||||
public ResourceFileExtractor(Assembly assembly, ResourceFileExtractor baseExtractor)
|
||||
: this(assembly ?? Assembly.GetCallingAssembly(), false, baseExtractor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance constructor
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="isStatic"></param>
|
||||
/// <param name="baseExtractor"></param>
|
||||
/// <exception cref="ArgumentNullException">Argument is null.</exception>
|
||||
private ResourceFileExtractor(Assembly assembly, bool isStatic, ResourceFileExtractor baseExtractor)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (assembly is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
Assembly = assembly;
|
||||
m_baseExtractor = baseExtractor;
|
||||
AssemblyName = Assembly.GetName().Name;
|
||||
IsStatic = isStatic;
|
||||
ResourceFilePath = ".Resources.";
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary> Work assembly </summary>
|
||||
public Assembly Assembly { get; }
|
||||
|
||||
/// <summary> Work assembly name </summary>
|
||||
public string AssemblyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to read resource files. Example: .Resources.Upgrades.
|
||||
/// </summary>
|
||||
public string ResourceFilePath { get; }
|
||||
|
||||
public bool IsStatic { get; set; }
|
||||
|
||||
public IEnumerable<string> GetFileNames(Func<String, Boolean> predicate = null)
|
||||
{
|
||||
predicate = predicate ?? (s => true);
|
||||
|
||||
var _path = AssemblyName + ResourceFilePath;
|
||||
foreach (string _resourceName in Assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (_resourceName.StartsWith(_path) && predicate(_resourceName))
|
||||
{
|
||||
yield return _resourceName.Replace(_path, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public properties
|
||||
|
||||
#region Public methods
|
||||
|
||||
public string ReadFileFromResource(string fileName)
|
||||
{
|
||||
var _stream = ReadFileFromResourceToStream(fileName);
|
||||
string _result;
|
||||
var sr = new StreamReader(_stream);
|
||||
try
|
||||
{
|
||||
_result = sr.ReadToEnd();
|
||||
}
|
||||
finally
|
||||
{
|
||||
sr.Close();
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
|
||||
public string ReadFileFromResourceFormat(string fileName, params object[] formatArgs)
|
||||
{
|
||||
return string.Format(ReadFileFromResource(fileName), formatArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read file in current assembly by specific path
|
||||
/// </summary>
|
||||
/// <param name="specificPath">Specific path</param>
|
||||
/// <param name="fileName">Read file name</param>
|
||||
/// <returns></returns>
|
||||
public string ReadSpecificFileFromResource(string specificPath, string fileName)
|
||||
{
|
||||
ResourceFileExtractor _ext = new ResourceFileExtractor(Assembly, specificPath);
|
||||
return _ext.ReadFileFromResource(fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read file in current assembly by specific file name
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ApplicationException"><c>ApplicationException</c>.</exception>
|
||||
public Stream ReadFileFromResourceToStream(string fileName)
|
||||
{
|
||||
var _nameResFile = AssemblyName + ResourceFilePath + fileName;
|
||||
var _stream = Assembly.GetManifestResourceStream(_nameResFile);
|
||||
|
||||
#region Not found
|
||||
|
||||
if (_stream is null)
|
||||
{
|
||||
#region Get from base extractor
|
||||
|
||||
if (!(m_baseExtractor is null))
|
||||
{
|
||||
return m_baseExtractor.ReadFileFromResourceToStream(fileName);
|
||||
}
|
||||
|
||||
#endregion Get from base extractor
|
||||
|
||||
throw new ArgumentException("Can't find resource file " + _nameResFile, nameof(fileName));
|
||||
}
|
||||
|
||||
#endregion Not found
|
||||
|
||||
return _stream;
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Help methods for work with streams
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class StreamHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert stream to byte array
|
||||
/// </summary>
|
||||
/// <param name="pStream">Stream</param>
|
||||
/// <returns>Byte array</returns>
|
||||
public static byte[] StreamToArray(Stream pStream)
|
||||
{
|
||||
long iLength = pStream.Length;
|
||||
var bytes = new byte[iLength];
|
||||
for (int i = 0; i < iLength; i++)
|
||||
{
|
||||
bytes[i] = (byte)pStream.ReadByte();
|
||||
}
|
||||
pStream.Close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert byte array to stream
|
||||
/// </summary>
|
||||
/// <param name="pBynaryArray">Byte array</param>
|
||||
/// <param name="pStream">Open stream</param>
|
||||
/// <returns></returns>
|
||||
public static Stream ArrayToStreamAppend(byte[] pBynaryArray, Stream pStream)
|
||||
{
|
||||
#region Check params
|
||||
|
||||
if (ReferenceEquals(pBynaryArray, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pBynaryArray));
|
||||
}
|
||||
if (ReferenceEquals(pStream, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pStream));
|
||||
}
|
||||
if (!pStream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException("Can't write to stream", nameof(pStream));
|
||||
}
|
||||
|
||||
#endregion Check params
|
||||
|
||||
foreach (byte b in pBynaryArray)
|
||||
{
|
||||
pStream.WriteByte(b);
|
||||
}
|
||||
return pStream;
|
||||
}
|
||||
|
||||
public static void StreamToStreamAppend(Stream streamIn, Stream streamToWrite)
|
||||
{
|
||||
StreamToStreamAppend(streamIn, streamToWrite, 0);
|
||||
}
|
||||
|
||||
public static void StreamToStreamAppend(Stream streamIn, Stream streamToWrite, long dataLength)
|
||||
{
|
||||
#region Check params
|
||||
|
||||
if (ReferenceEquals(streamIn, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(streamIn));
|
||||
}
|
||||
if (ReferenceEquals(streamToWrite, null))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(streamToWrite));
|
||||
}
|
||||
if (!streamIn.CanRead)
|
||||
{
|
||||
throw new ArgumentException("Can't read from stream", nameof(streamIn));
|
||||
}
|
||||
if (!streamToWrite.CanWrite)
|
||||
{
|
||||
throw new ArgumentException("Can't write to stream", nameof(streamToWrite));
|
||||
}
|
||||
|
||||
#endregion Check params
|
||||
|
||||
var buf = new byte[512];
|
||||
long length;
|
||||
if (dataLength == 0)
|
||||
{
|
||||
length = streamIn.Length - streamIn.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
length = dataLength;
|
||||
}
|
||||
long rest = length;
|
||||
while (rest > 0)
|
||||
{
|
||||
int len1 = streamIn.Read(buf, 0, rest >= 512 ? 512 : (int)rest);
|
||||
streamToWrite.Write(buf, 0, len1);
|
||||
rest -= len1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two streams by converting them to strings and comparing the strings
|
||||
/// </summary>
|
||||
/// <param name="one"></param>
|
||||
/// <param name="other"></param>
|
||||
/// /// <param name="stripColumnWidths"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Compare(Tuple<Uri, Stream> tuple1, Tuple<Uri, Stream> tuple2, bool stripColumnWidths)
|
||||
{
|
||||
#region Check
|
||||
|
||||
if (tuple1 == null || tuple1.Item1 == null || tuple1.Item2 == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tuple1));
|
||||
}
|
||||
if (tuple2 == null || tuple2.Item1 == null || tuple2.Item2 == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tuple2));
|
||||
}
|
||||
if (tuple1.Item2.Position != 0)
|
||||
{
|
||||
throw new ArgumentException("Must be in position 0", nameof(tuple1));
|
||||
}
|
||||
if (tuple2.Item2.Position != 0)
|
||||
{
|
||||
throw new ArgumentException("Must be in position 0", nameof(tuple2));
|
||||
}
|
||||
|
||||
#endregion Check
|
||||
|
||||
var stringOne = new StreamReader(tuple1.Item2).ReadToEnd().RemoveIgnoredParts(tuple1.Item1, stripColumnWidths, ignoreGuids: true);
|
||||
var stringOther = new StreamReader(tuple2.Item2).ReadToEnd().RemoveIgnoredParts(tuple2.Item1, stripColumnWidths, ignoreGuids: true);
|
||||
return stringOne == stringOther;
|
||||
}
|
||||
|
||||
private static string RemoveIgnoredParts(this string s, Uri uri, bool ignoreColumnWidths, bool ignoreGuids)
|
||||
{
|
||||
s = uriSpecificIgnores.Where(p => p.Key.Equals(uri.OriginalString)).Aggregate(s, (current, pair) => pair.Value.Replace(current, ""));
|
||||
|
||||
// Collapse empty xml elements
|
||||
s = emptyXmlElementRegex.Replace(s, "<$1 />");
|
||||
|
||||
if (ignoreColumnWidths)
|
||||
s = RemoveColumnWidths(s);
|
||||
|
||||
if (ignoreGuids)
|
||||
s = RemoveGuids(s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private static IEnumerable<KeyValuePair<string, Regex>> uriSpecificIgnores = new List<KeyValuePair<string, Regex>>()
|
||||
{
|
||||
// Remove dcterms elements
|
||||
new KeyValuePair<string, Regex>("/docProps/core.xml", new Regex(@"<dcterms:(\w+).*?<\/dcterms:\1>", RegexOptions.Compiled))
|
||||
};
|
||||
|
||||
private static Regex emptyXmlElementRegex = new Regex(@"<([\w:]+)><\/\1>", RegexOptions.Compiled);
|
||||
private static Regex columnRegex = new Regex("<x:col.*?width=\"\\d+(\\.\\d+)?\".*?\\/>", RegexOptions.Compiled);
|
||||
private static Regex widthRegex = new Regex("width=\"\\d+(\\.\\d+)?\"\\s+", RegexOptions.Compiled);
|
||||
|
||||
private static string RemoveColumnWidths(string s)
|
||||
{
|
||||
var replacements = new Dictionary<string, string>();
|
||||
|
||||
foreach (var m in columnRegex.Matches(s).OfType<Match>())
|
||||
{
|
||||
var original = m.Groups[0].Value;
|
||||
var replacement = widthRegex.Replace(original, "");
|
||||
replacements.Add(original, replacement);
|
||||
}
|
||||
|
||||
return replacements.Aggregate(s, (current, r) => current.Replace(r.Key, r.Value));
|
||||
}
|
||||
|
||||
private static Regex guidRegex = new Regex(@"{[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}}", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
private static string RemoveGuids(string s) => guidRegex.Replace(s, m => string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using VerifyTests;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
public static class TestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The test name
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns>
|
||||
/// The name of the test split by camel case for a max length of 200 characters.
|
||||
/// </returns>
|
||||
public static string TestName(this TestContext context, string suffix = "")
|
||||
{
|
||||
var testName = suffix == string.Empty
|
||||
? $"{context.Test.Name}".SplitCamelCase()
|
||||
: $"{context.Test.Name} - {suffix}".SplitCamelCase();
|
||||
testName = testName[..Math.Min(200, testName.Length)];
|
||||
return testName;
|
||||
}
|
||||
|
||||
public static string SplitCamelCase(this string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) { return input; }
|
||||
|
||||
const string pattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-r + t-z])|[A-Z](?=[A-Z][s][a-z]))";
|
||||
|
||||
if (input.StartsWith("FY", StringComparison.Ordinal))
|
||||
{ // hack for FY2012, etc
|
||||
return "FY " + Regex.Replace(input.Remove(0, 2), pattern, "$1 ");
|
||||
}
|
||||
input = Regex.Replace(input, @"\{|\}|:|,|\)|\(", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
return Regex.Replace(input, pattern, "$1 ");
|
||||
}
|
||||
|
||||
public static VerifySettings TestSettings()
|
||||
{
|
||||
var settings = new VerifySettings();
|
||||
settings.UseDirectory("snapshots");
|
||||
settings.ScrubInlineGuids();
|
||||
return settings;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using ClosedXML.Excel;
|
||||
using FluentAssertions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal static class TestHelper
|
||||
{
|
||||
public static string CurrencySymbol => Thread.CurrentThread.CurrentCulture.NumberFormat.CurrencySymbol;
|
||||
|
||||
public static string TestsRunDirectory => System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
public static string TestsRootDirectory => Path.GetFullPath(Path.Combine(TestsRunDirectory, @"../../../../"));
|
||||
//Note: Run example tests parameters
|
||||
public static string TestsOutputDirectory => Path.Combine(Path.GetDirectoryName(TestsRunDirectory), "Generated");
|
||||
|
||||
public const string ActualTestResultPostFix = "";
|
||||
public static readonly string ExampleTestsOutputDirectory = Path.Combine(TestsOutputDirectory, "Examples");
|
||||
|
||||
private const bool CompareWithResources = true;
|
||||
|
||||
private static readonly ResourceFileExtractor _extractor = new ResourceFileExtractor(".Resource.");
|
||||
|
||||
public static void SaveWorkbook(XLWorkbook workbook, params string[] fileNameParts)
|
||||
{
|
||||
workbook.SaveAs(Path.Combine(new string[] { TestsOutputDirectory }.Concat(fileNameParts).ToArray()), true);
|
||||
}
|
||||
|
||||
// Because different fonts are installed on Unix,
|
||||
// the columns widths after AdjustToContents() will
|
||||
// cause the tests to fail.
|
||||
// Therefore we ignore the width attribute when running on Unix
|
||||
public static bool StripColumnWidths => IsRunningOnUnix;
|
||||
|
||||
public static bool IsRunningOnUnix
|
||||
{
|
||||
get
|
||||
{
|
||||
var p = (int)Environment.OSVersion.Platform;
|
||||
return ((p == 4) || (p == 6) || (p == 128));
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunTestExample<T>(string filePartName, bool evaluateFormulae = false)
|
||||
where T : IXLExample, new()
|
||||
{
|
||||
// Make sure tests run on a deterministic culture
|
||||
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
|
||||
|
||||
var example = new T();
|
||||
var pathParts = filePartName.Split(new char[] { '\\' });
|
||||
var filePath1 = Path.Combine(new List<string>() { ExampleTestsOutputDirectory }.Concat(pathParts).ToArray());
|
||||
|
||||
var extension = Path.GetExtension(filePath1);
|
||||
var directory = Path.GetDirectoryName(filePath1);
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath1);
|
||||
fileName += ActualTestResultPostFix;
|
||||
fileName = Path.ChangeExtension(fileName, extension);
|
||||
|
||||
filePath1 = Path.Combine(directory, "z" + fileName);
|
||||
var filePath2 = Path.Combine(directory, fileName);
|
||||
|
||||
//Run test
|
||||
example.Create(filePath1);
|
||||
using (var wb = new XLWorkbook(filePath1))
|
||||
wb.SaveAs(filePath2, validate: true, evaluateFormulae);
|
||||
|
||||
// Also load from template and save it again - but not necessary to test against reference file
|
||||
// We're just testing that it can save.
|
||||
using (var ms = new MemoryStream())
|
||||
using (var wb = XLWorkbook.OpenFromTemplate(filePath1))
|
||||
wb.SaveAs(ms, validate: true, evaluateFormulae);
|
||||
|
||||
var resourcePath = "Examples." + filePartName.Replace('\\', '.').TrimStart('.');
|
||||
using (var streamExpected = _extractor.ReadFileFromResourceToStream(resourcePath))
|
||||
using (var streamActual = File.OpenRead(filePath2))
|
||||
{
|
||||
var success = ExcelDocsComparer.Compare(streamActual, streamExpected, out string message);
|
||||
var formattedMessage =
|
||||
$"Actual file '{filePath2}' is different than the expected file '{resourcePath}'. The difference is: '{message}'";
|
||||
success.Should().BeTrue(formattedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateAndCompare(Func<IXLWorkbook> workbookGenerator, string referenceResource, bool evaluateFormulae = false)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
|
||||
|
||||
var pathParts = referenceResource.Split(new char[] { '\\' });
|
||||
var filePath1 = Path.Combine(new List<string>() { TestsOutputDirectory }.Concat(pathParts).ToArray());
|
||||
|
||||
var extension = Path.GetExtension(filePath1);
|
||||
var directory = Path.GetDirectoryName(filePath1);
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath1);
|
||||
fileName += ActualTestResultPostFix;
|
||||
fileName = Path.ChangeExtension(fileName, extension);
|
||||
|
||||
var filePath2 = Path.Combine(directory, fileName);
|
||||
|
||||
using (var wb = workbookGenerator.Invoke())
|
||||
wb.SaveAs(filePath2, true, evaluateFormulae);
|
||||
|
||||
var resourcePath = referenceResource.Replace('\\', '.').TrimStart('.');
|
||||
using (var streamExpected = _extractor.ReadFileFromResourceToStream(resourcePath))
|
||||
using (var streamActual = File.OpenRead(filePath2))
|
||||
{
|
||||
var success = ExcelDocsComparer.Compare(streamActual, streamExpected, out string message);
|
||||
var formattedMessage =
|
||||
$"Actual file '{filePath2}' is different than the expected file '{resourcePath}'. The difference is: '{message}'";
|
||||
success.Should().BeTrue(formattedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetResourcePath(string filePartName)
|
||||
{
|
||||
return filePartName.Replace('\\', '.').TrimStart('.');
|
||||
}
|
||||
|
||||
public static Stream GetStreamFromResource(string resourcePath)
|
||||
{
|
||||
return _extractor.ReadFileFromResourceToStream(resourcePath);
|
||||
}
|
||||
|
||||
public static void LoadFile(string filePartName)
|
||||
{
|
||||
IXLWorkbook wb;
|
||||
using var stream = GetStreamFromResource(GetResourcePath(filePartName));
|
||||
Action action = () =>
|
||||
{
|
||||
wb = new XLWorkbook(stream);
|
||||
};
|
||||
action.Should().NotThrow($"Unable to load resource {filePartName}");
|
||||
}
|
||||
|
||||
public static IEnumerable<String> ListResourceFiles(Func<String, Boolean> predicate = null)
|
||||
{
|
||||
return _extractor.GetFileNames(predicate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Moq;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Utilities
|
||||
{
|
||||
public static class TestUtilities
|
||||
{
|
||||
public const string UserGuid = "eb7e4993-c65a-493a-9614-986a55dd0744";
|
||||
public const string DbGuid = "54409f55-5ddf-4748-8222-9ccfb04fb4f5";
|
||||
public const string ClientDbGuid = "1742934B-C508-402B-B8B5-D18C506474CB";
|
||||
public const string Username = "a";
|
||||
public const string Key = UserGuid + Username;
|
||||
public const string DbVersion = "202133";
|
||||
|
||||
public static IClaimsPrincipalAccessor GetClaimsPrincipalAccessor(string userGuid = null, string userName = null,
|
||||
bool isSdtEmployee = true, bool isClientDatabase = false, bool isEmptyDatabase = false, int strataId = 0)
|
||||
{
|
||||
var dbGuid = DbGuid;
|
||||
if (isClientDatabase) { dbGuid = ClientDbGuid; }
|
||||
if (isEmptyDatabase) { dbGuid = Guid.Empty.ToString(); }
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, "Name"),
|
||||
new Claim(StrataClaims.UserGuid, userGuid ?? UserGuid),
|
||||
new Claim(StrataClaims.Username, userName ?? Username),
|
||||
new Claim("given_name", "Firstname"),
|
||||
new Claim("family_name", "Lastname"),
|
||||
new Claim(StrataClaims.FirstName, "Firstname"),
|
||||
new Claim(StrataClaims.LastName, "Lastname"),
|
||||
new Claim(StrataClaims.DatabaseVersion, DbVersion),
|
||||
new Claim("strata_id", strataId.ToString()),
|
||||
new Claim(StrataClaims.DatabaseGuid, dbGuid),
|
||||
new Claim(StrataClaims.IsSdtEmployee, isSdtEmployee.ToString())
|
||||
};
|
||||
var claimIdentity = new ClaimsIdentity(claims, "mock");
|
||||
var claimsPrincipal = new ClaimsPrincipal(new[] { claimIdentity });
|
||||
var claimsPrincipalAccessorMock = new Mock<IClaimsPrincipalAccessor>();
|
||||
claimsPrincipalAccessorMock.Setup(s => s.GetCurrentClaimsPrincipal()).Returns(claimsPrincipal);
|
||||
|
||||
return claimsPrincipalAccessorMock.Object;
|
||||
}
|
||||
|
||||
internal static IFeatureFlagServiceClient GetFeatureFlagServiceClient(string featureFlag, bool isOn = true)
|
||||
{
|
||||
var featureFlagServiceClient = Mock.Of<IFeatureFlagServiceClient>();
|
||||
Mock.Get(featureFlagServiceClient)
|
||||
.Setup(s => s.IsEnabledAsync(It.IsAny<string>(), It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<string, Guid, bool, bool, CancellationToken>((flag, dbGuid, defaultValue, returnDefaultValue, token) => Task.FromResult(flag == featureFlag && dbGuid.ToString() == DbGuid ? isOn : !isOn));
|
||||
return featureFlagServiceClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user