diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5f935e..d42e0cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [3.4.0] + +## Added + +- **Alpha**: Introduced subscription usage monitoring based on customer feedback + ## [3.3.3] ### Added diff --git a/src/FakeXrmEasy.Core/CommercialLicense/EnvironmentReader.cs b/src/FakeXrmEasy.Core/CommercialLicense/EnvironmentReader.cs new file mode 100644 index 00000000..a92c1d8b --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/EnvironmentReader.cs @@ -0,0 +1,17 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + internal interface IEnvironmentReader + { + string GetEnvironmentVariable(string variableName); + } + + internal class EnvironmentReader: IEnvironmentReader + { + public string GetEnvironmentVariable(string variableName) + { + return Environment.GetEnvironmentVariable(variableName); + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/ConsiderUpgradingException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/ConsiderUpgradingException.cs new file mode 100644 index 00000000..bb9c1603 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/ConsiderUpgradingException.cs @@ -0,0 +1,21 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Exception raised when the current number of users calculated based on the usage of your current subscription is greater than the maximum number of users in your current subscription + /// + public class ConsiderUpgradingPlanException: Exception + { + /// + /// Default constructor + /// + /// + /// + public ConsiderUpgradingPlanException(long currentNumberOfUsers, long allowedNumberOfUsers) : + base($"Your current subscription allows up to {allowedNumberOfUsers.ToString()}, however, {currentNumberOfUsers.ToString()} are currently using it. Please consider upgrading your current plan.") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/InvalidLicenseKeyException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/InvalidLicenseKeyException.cs new file mode 100644 index 00000000..7879e1e6 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/InvalidLicenseKeyException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Exception raised when the license key is invalid or malformed + /// + public class InvalidLicenseKeyException: Exception + { + /// + /// Default constructor + /// + public InvalidLicenseKeyException() : base("The license key is invalid") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoSubscriptionPlanInfoException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoSubscriptionPlanInfoException.cs new file mode 100644 index 00000000..5d9c63e9 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoSubscriptionPlanInfoException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Exception thrown if the info about the current subscription plan is unknown + /// + public class NoSubscriptionPlanInfoException: Exception + { + /// + /// Default constructor + /// + public NoSubscriptionPlanInfoException() : base("The current subscription info is unknown") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoUsageFoundException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoUsageFoundException.cs new file mode 100644 index 00000000..0410493e --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/NoUsageFoundException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Throws an exception when your current usage of FakeXrmEasy could not be retrieved + /// + public class NoUsageFoundException: Exception + { + /// + /// Default constructor + /// + public NoUsageFoundException() : base("No info about your current usage of FakeXrmEasy was found") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/RenewalRequestExpiredException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/RenewalRequestExpiredException.cs new file mode 100644 index 00000000..97c360d8 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/RenewalRequestExpiredException.cs @@ -0,0 +1,19 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Exception raised when your current subscription expired and you exceeded the allowed renewal time window + /// + public class RenewalRequestExpiredException: Exception + { + /// + /// Throws an exception where the current subscription expired + /// + /// + public RenewalRequestExpiredException(DateTime expiredOn) : base($"The current subscription expired on '{expiredOn.ToLongDateString()}' and a renewal license was not applied on time. Please request a new subscription license.") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/SubscriptionExpiredException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/SubscriptionExpiredException.cs new file mode 100644 index 00000000..067264f1 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/SubscriptionExpiredException.cs @@ -0,0 +1,19 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// The current subscription expired + /// + public class SubscriptionExpiredException: Exception + { + /// + /// Throws an exception where the current subscription expired + /// + /// + public SubscriptionExpiredException(DateTime expiredOn) : base($"The current subscription expired on {expiredOn.ToLongDateString()}") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/UpgradeRequestExpiredException.cs b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/UpgradeRequestExpiredException.cs new file mode 100644 index 00000000..dffc10a3 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/Exceptions/UpgradeRequestExpiredException.cs @@ -0,0 +1,20 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense.Exceptions +{ + /// + /// Exception raised when the grace period for requesting an upgrade has expired + /// + public class UpgradeRequestExpiredException: Exception + { + /// + /// Default constructor + /// + /// + public UpgradeRequestExpiredException(DateTime firstRequested) : + base($"You requested a subscription upgrade on '{firstRequested.ToShortDateString()}', however, the new subscription details or upgrade progress has not been completed within the allowed upgrade window. Please contact your line manager and raise a support ticket") + { + + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionInfo.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionInfo.cs new file mode 100644 index 00000000..d237ce0d --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionInfo.cs @@ -0,0 +1,46 @@ +using System; +using FakeXrmEasy.Abstractions.CommercialLicense; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + /// + /// Contains info about the current subscription + /// + internal class SubscriptionInfo: ISubscriptionInfo + { + /// + /// The CustomerId + /// + public string CustomerId { get; set; } + + /// + /// SKU + /// + public StockKeepingUnits SKU { get; set; } + + /// + /// True if the current subscription auto-renews + /// + public bool AutoRenews { get; set; } + + /// + /// The current billing cycle type + /// + public SubscriptionBillingCycleType BillingType { get; set; } + + /// + /// Max number of users allowed in the current subscription + /// + public long NumberOfUsers { get; set; } + + /// + /// The subscription start date + /// + public DateTime StartDate { get; set; } + + /// + /// The subscription's end date + /// + public DateTime EndDate { get; set; } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionManager.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionManager.cs new file mode 100644 index 00000000..00a9a9a2 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionManager.cs @@ -0,0 +1,53 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + internal static class SubscriptionManager + { + internal static ISubscriptionInfo _subscriptionInfo; + internal static readonly object _subscriptionInfoLock = new object(); + + internal static ISubscriptionUsage _subscriptionUsage; + internal static readonly object _subscriptionUsageLock = new object(); + + internal static bool _renewalRequested = false; + internal static bool _upgradeRequested = false; + + private static void SetLicenseKey(string licenseKey) + { + lock (_subscriptionInfoLock) + { + if (_subscriptionInfo == null) + { + var subscriptionPlanManager = new SubscriptionPlanManager(); + _subscriptionInfo = subscriptionPlanManager.GetSubscriptionInfoFromKey(licenseKey); + } + } + } + + internal static void SetSubscriptionStorageProvider(ISubscriptionStorageProvider subscriptionStorageProvider, + IUserReader userReader, + bool upgradeRequested, + bool renewalRequested) + { + SetLicenseKey(subscriptionStorageProvider.GetLicenseKey()); + + lock (_subscriptionUsageLock) + { + if (_subscriptionUsage == null) + { + _upgradeRequested = upgradeRequested; + _renewalRequested = renewalRequested; + + var usageManager = new SubscriptionUsageManager(); + _subscriptionUsage = usageManager.ReadAndUpdateUsage(_subscriptionInfo, subscriptionStorageProvider, userReader, _upgradeRequested); + } + } + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionPlanManager.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionPlanManager.cs new file mode 100644 index 00000000..612e28d1 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionPlanManager.cs @@ -0,0 +1,64 @@ +using System; +using System.Globalization; +using System.Text; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + internal class SubscriptionPlanManager + { + internal string GenerateHash(string input) + { + using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + byte[] hashBytes = md5.ComputeHash(inputBytes); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hashBytes.Length; i++) + { + sb.Append(hashBytes[i].ToString("X2")); + } + return sb.ToString(); + } + } + + internal ISubscriptionInfo GetSubscriptionInfoFromKey(string licenseKey) + { + try + { + var encodedBaseKey = licenseKey.Substring(0, licenseKey.Length - 32); + var hash = licenseKey.Substring(licenseKey.Length - 32, 32); + var computedHash = GenerateHash(encodedBaseKey); + + if (!computedHash.Equals(hash)) + { + throw new InvalidLicenseKeyException(); + } + + var decodedBaseKey = Encoding.UTF8.GetString(Convert.FromBase64String(encodedBaseKey)); + var baseKeyParts = decodedBaseKey.Split('-'); + + var expiryDate = DateTime.ParseExact(baseKeyParts[4], "yyyyMMdd", CultureInfo.InvariantCulture); + var numberOfUsers = int.Parse(baseKeyParts[3]); + + var sku = (StockKeepingUnits) Enum.Parse(typeof(StockKeepingUnits), baseKeyParts[0]); + var autoRenews = "1".Equals(baseKeyParts[2]); + + return new SubscriptionInfo() + { + SKU = sku, + CustomerId = baseKeyParts[1], + NumberOfUsers = numberOfUsers, + EndDate = expiryDate, + AutoRenews = autoRenews + }; + } + catch + { + throw new InvalidLicenseKeyException(); + } + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUpgradeRequest.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUpgradeRequest.cs new file mode 100644 index 00000000..1082a57e --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUpgradeRequest.cs @@ -0,0 +1,11 @@ +using System; +using FakeXrmEasy.Abstractions.CommercialLicense; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + internal class SubscriptionUpgradeRequest: ISubscriptionUpgradeRequest + { + public DateTime FirstRequestDate { get; set; } + public long PreviousNumberOfUsers { get; set; } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsage.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsage.cs new file mode 100644 index 00000000..b9c4df98 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsage.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using FakeXrmEasy.Abstractions.CommercialLicense; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + /// + /// Contains info about the current subscription usage + /// + internal class SubscriptionUsage: ISubscriptionUsage + { + /// + /// The last time the current subscription usage was checked + /// + public DateTime LastTimeChecked { get; set; } + + /// + /// Information about all the users + /// + public ICollection Users { get; set; } + + /// + /// Contains info for a requested upgrade + /// + public ISubscriptionUpgradeRequest UpgradeInfo { get; set; } + + internal SubscriptionUsage() + { + Users = new List(); + LastTimeChecked = DateTime.UtcNow; + UpgradeInfo = null; + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsageManager.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsageManager.cs new file mode 100644 index 00000000..f86c68c3 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsageManager.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using FakeXrmEasy.Abstractions.CommercialLicense; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + internal class SubscriptionUsageManager + { + internal ISubscriptionUsage _subscriptionUsage; + + internal ISubscriptionUsage ReadAndUpdateUsage( + ISubscriptionInfo subscriptionInfo, + ISubscriptionStorageProvider subscriptionStorageProvider, + IUserReader userReader, + bool upgradeRequested) + { + _subscriptionUsage = subscriptionStorageProvider.Read(); + if (_subscriptionUsage == null) + { + _subscriptionUsage = new SubscriptionUsage(); + } + + var currentUserName = userReader.GetCurrentUserName(); + + var existingUser = _subscriptionUsage + .Users + .FirstOrDefault(user => currentUserName.Equals(user.UserName)); + + if (existingUser == null) + { + _subscriptionUsage.Users.Add(new SubscriptionUserInfo() + { + UserName = currentUserName, + LastTimeUsed = DateTime.UtcNow + }); + } + else + { + existingUser.LastTimeUsed = DateTime.UtcNow; + } + + if (upgradeRequested && _subscriptionUsage.UpgradeInfo == null) + { + _subscriptionUsage.UpgradeInfo = new SubscriptionUpgradeRequest() + { + FirstRequestDate = DateTime.UtcNow, + PreviousNumberOfUsers = subscriptionInfo.NumberOfUsers + }; + } + + subscriptionStorageProvider.Write(_subscriptionUsage); + + return _subscriptionUsage; + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUserInfo.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUserInfo.cs new file mode 100644 index 00000000..5d999af1 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUserInfo.cs @@ -0,0 +1,21 @@ +using System; +using FakeXrmEasy.Abstractions.CommercialLicense; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + /// + /// Info about the last time a given user used FakeXrmEasy + /// + internal class SubscriptionUserInfo: ISubscriptionUserInfo + { + /// + /// The last time this user used FakeXrmEasy + /// + public DateTime LastTimeUsed { get; set; } + + /// + /// The user's username + /// + public string UserName { get; set; } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionValidator.cs b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionValidator.cs new file mode 100644 index 00000000..220c97a4 --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/SubscriptionValidator.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + /// + /// Validates the current subscription usage is within the current subscription plan + /// + internal sealed class SubscriptionValidator + { + private readonly IEnvironmentReader _environmentReader; + private readonly ISubscriptionInfo _subscriptionInfo; + private readonly ISubscriptionUsage _subscriptionUsage; + private readonly bool _renewalRequested; + + internal SubscriptionValidator( + IEnvironmentReader environmentReader, + ISubscriptionInfo subscriptionInfo, + ISubscriptionUsage subscriptionUsage, + bool renewalRequested) + { + _environmentReader = environmentReader; + _subscriptionInfo = subscriptionInfo; + _subscriptionUsage = subscriptionUsage; + _renewalRequested = renewalRequested; + } + + /// + /// Validates if the current usage is within the subscription limits + /// + /// + internal bool IsValid() + { + var isSubscriptionPlanValid = IsSubscriptionPlanValid(); + if (!isSubscriptionPlanValid) + { + return false; + } + + var isSubscriptionUsageValid = IsUsageValid(); + if (!isSubscriptionUsageValid) + { + return false; + } + + return true; + } + + /// + /// Returns valid if the current subscription didn't expire yet + /// + /// + /// + /// + internal bool IsSubscriptionPlanValid() + { + if (_subscriptionInfo == null) + { + throw new NoSubscriptionPlanInfoException(); + } + + if (_subscriptionInfo.AutoRenews) + { + return true; + } + + var expiryDate = _subscriptionInfo.EndDate; + + if (expiryDate < DateTime.UtcNow) + { + if (!_renewalRequested) + { + throw new SubscriptionExpiredException(expiryDate); + } + else + { + if (expiryDate.AddMonths(1) < DateTime.UtcNow) + { + throw new RenewalRequestExpiredException(expiryDate); + } + } + } + + return true; + } + + internal bool IsUsageValid() + { + if (IsRunningInContinuousIntegration()) + { + return true; + } + if (_subscriptionUsage == null) + { + throw new NoUsageFoundException(); + } + + var currentNumberOfUsers = _subscriptionUsage + .Users + .Count(userInfo => userInfo.LastTimeUsed >= DateTime.UtcNow.AddMonths(-1)); + + if (currentNumberOfUsers > _subscriptionInfo.NumberOfUsers) + { + if (_subscriptionUsage.UpgradeInfo == null) + { + throw new ConsiderUpgradingPlanException(currentNumberOfUsers, _subscriptionInfo.NumberOfUsers); + } + else + { + if (_subscriptionUsage.UpgradeInfo.FirstRequestDate <= DateTime.UtcNow.AddMonths(-1)) + { + throw new UpgradeRequestExpiredException(_subscriptionUsage.UpgradeInfo.FirstRequestDate); + } + } + + } + return true; + } + + private bool IsRunningInContinuousIntegration() + { + return "1".Equals(_environmentReader.GetEnvironmentVariable("FAKE_XRM_EASY_CI")) + || "True".Equals(_environmentReader.GetEnvironmentVariable("TF_BUILD")); + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/CommercialLicense/UserReader.cs b/src/FakeXrmEasy.Core/CommercialLicense/UserReader.cs new file mode 100644 index 00000000..e65e0dee --- /dev/null +++ b/src/FakeXrmEasy.Core/CommercialLicense/UserReader.cs @@ -0,0 +1,24 @@ +using System; + +namespace FakeXrmEasy.Core.CommercialLicense +{ + /// + /// Returns info about the current user + /// + public interface IUserReader + { + /// + /// Gets the current username + /// + /// + string GetCurrentUserName(); + } + + internal class UserReader: IUserReader + { + public string GetCurrentUserName() + { + return Environment.UserName; + } + } +} \ No newline at end of file diff --git a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj index 6a0eaa15..72fee4ad 100644 --- a/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj +++ b/src/FakeXrmEasy.Core/FakeXrmEasy.Core.csproj @@ -8,7 +8,7 @@ net6.0 net6.0 FakeXrmEasy.Core - 3.3.3 + 3.4.0 Jordi Montaña Dynamics Value FakeXrmEasy Core @@ -70,25 +70,25 @@ - + - + - + - + - + - + - + diff --git a/src/FakeXrmEasy.Core/Middleware/MiddlewareBuilder.cs b/src/FakeXrmEasy.Core/Middleware/MiddlewareBuilder.cs index 88c15192..b82853a7 100644 --- a/src/FakeXrmEasy.Core/Middleware/MiddlewareBuilder.cs +++ b/src/FakeXrmEasy.Core/Middleware/MiddlewareBuilder.cs @@ -6,12 +6,14 @@ using System.Linq; using Microsoft.Xrm.Sdk; using FakeItEasy; +using FakeXrmEasy.Abstractions.CommercialLicense; using FakeXrmEasy.Abstractions.Integrity; using FakeXrmEasy.Abstractions.Enums; using FakeXrmEasy.Abstractions.Exceptions; using Microsoft.Xrm.Sdk.Query; using Microsoft.PowerPlatform.Dataverse.Client; using System.Threading; +using FakeXrmEasy.Core.CommercialLicense; using FakeXrmEasy.Core.Exceptions; namespace FakeXrmEasy.Middleware @@ -90,6 +92,21 @@ public IXrmFakedContext Build() throw new LicenseException("Please, you need to choose a FakeXrmEasy license. More info at https://dynamicsvalue.github.io/fake-xrm-easy-docs/licensing/licensing-exception/"); } + if (_context.LicenseContext == FakeXrmEasyLicense.Commercial) + { + var subscriptionInfo = SubscriptionManager._subscriptionInfo; + if (subscriptionInfo != null) + { + var subscriptionValidator = new SubscriptionValidator( + new EnvironmentReader(), + SubscriptionManager._subscriptionInfo, + SubscriptionManager._subscriptionUsage, + SubscriptionManager._renewalRequested); + + subscriptionValidator.IsValid(); + } + } + OrganizationRequestDelegate app = (context, request) => { //return default PullRequestException at the end of the pipeline @@ -117,7 +134,7 @@ public IXrmFakedContext Build() } /// - /// + /// FakeXrmEasy can be used under 3 different licences, this method defines the license. More info at: https://dynamicsvalue.github.io/fake-xrm-easy-docs/licensing/license/ /// /// /// @@ -126,6 +143,19 @@ public IMiddlewareBuilder SetLicense(FakeXrmEasyLicense license) _context.LicenseContext = license; return this; } + + /// + /// Use this method to provide an implementation for a subscription storage provider when you are using a commercial license and have a license key + /// + /// An implementation of a ISubscriptionStorageProvider that is capable of reading and writing subscription usage data as well as your license key + /// Set to true if you exceeded the number of users that your current subscription allows and you have already requested an upgrade to DynamicsValue via your organisation's established process + /// Set to true if your subscription expired and you have already requested an renewal to DynamicsValue via your organisation's established process + /// + public IMiddlewareBuilder SetSubscriptionStorageProvider(ISubscriptionStorageProvider storageProvider, bool upgradeRequested = false, bool renewalRequested = false) + { + SubscriptionManager.SetSubscriptionStorageProvider(storageProvider, new UserReader(), upgradeRequested, renewalRequested); + return this; + } private void AddOrganizationServiceAsyncFake(IOrganizationServiceAsync serviceAsync) { diff --git a/src/FakeXrmEasy.Core/XrmFakedContext.cs b/src/FakeXrmEasy.Core/XrmFakedContext.cs index 215c5496..ff58f350 100644 --- a/src/FakeXrmEasy.Core/XrmFakedContext.cs +++ b/src/FakeXrmEasy.Core/XrmFakedContext.cs @@ -21,6 +21,8 @@ using System.Reflection; using System.Runtime.CompilerServices; +using FakeXrmEasy.Abstractions.CommercialLicense; + [assembly: InternalsVisibleTo("FakeXrmEasy.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c124cb50761165a765adf6078bde555a7c5a2b692ed6e6ec9df0bd7d20da69170bae9bf95e874fa50995cc080af404ccad36515fa509c4ea6599a0502c1642db254a293e023c47c79ce69889c6ba921d124d896d87f0baaa9ea1d87b28589ffbe7b08492606bacef19dc4bc4cefb0d525be63ee722b02dc8c79688a7a8f623a2")] namespace FakeXrmEasy @@ -316,7 +318,7 @@ public virtual void Initialize(IEnumerable entities) /// /// Initializes the context with a single entity record /// - /// + /// Entity record that will be used to initialize the In-Memory context public void Initialize(Entity entity) { this.Initialize(new List() { entity }); diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/FakeEnvironmentReader.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/FakeEnvironmentReader.cs new file mode 100644 index 00000000..6daaa53e --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/FakeEnvironmentReader.cs @@ -0,0 +1,32 @@ +using System.Collections.Concurrent; +using FakeXrmEasy.Core.CommercialLicense; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public class FakeEnvironmentReader : IEnvironmentReader + { + private readonly ConcurrentDictionary _variables; + + public FakeEnvironmentReader() + { + _variables = new ConcurrentDictionary(); + } + + public string GetEnvironmentVariable(string variableName) + { + string variableValue = ""; + var exists = _variables.TryGetValue(variableName, out variableValue); + if (!exists) + { + return null; + } + + return variableValue; + } + + public void SetEnvironmentVariable(string variableName, string variableValue) + { + _variables.AddOrUpdate(variableName, variableValue, (key, oldValue) => variableValue); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionManagerTests.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionManagerTests.cs new file mode 100644 index 00000000..171e3eab --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionManagerTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FakeItEasy; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public class SubscriptionManagerTests + { + + + + + + + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionPlanManagerTests.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionPlanManagerTests.cs new file mode 100644 index 00000000..4cb5e73c --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionPlanManagerTests.cs @@ -0,0 +1,34 @@ +using FakeXrmEasy.Core.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public class SubscriptionPlanManagerTests + { + private readonly SubscriptionPlanManager _subscriptionPlanManager; + + public SubscriptionPlanManagerTests() + { + _subscriptionPlanManager = new SubscriptionPlanManager(); + } + + [Fact] + public void Should_raise_invalid_license_key_exception() + { + var invalidKey = + "asdasdkjakdhu38768a79aysdaiushdakjshdajshda79878s97d89as7d9a87sda98sdyausydusydausdajbdahsdjhasgdahsgda78sda8s7d6a986d98as6d9a8d"; + + Assert.Throws(() => _subscriptionPlanManager.GetSubscriptionInfoFromKey(invalidKey)); + } + + [Fact] + public void Should_raise_invalid_license_key_exception_even_if_hash_matches_but_subscription_data_is_not_valid() + { + var invalidKey = + "asdasdkjakdhu38768a79aysdaiushdakjshdajshda79878s97d89as7d9a87sda98sdyausydusydausdajbdahsdj"; + + Assert.Throws(() => _subscriptionPlanManager.GetSubscriptionInfoFromKey($"{invalidKey}{_subscriptionPlanManager.GenerateHash(invalidKey)}")); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionUsageManagerTests.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionUsageManagerTests.cs new file mode 100644 index 00000000..3646336a --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionUsageManagerTests.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FakeItEasy; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public class SubscriptionUsageManagerTests + { + private readonly SubscriptionUsageManager _usageManager; + private readonly ISubscriptionStorageProvider _subscriptionStorageProvider; + private readonly IUserReader _userReader; + private const string cUserName = "CurrentDomain\\CurrentUser"; + private readonly ISubscriptionInfo _subscriptionInfo; + + public SubscriptionUsageManagerTests() + { + _subscriptionStorageProvider = A.Fake(); + _userReader = A.Fake(); + A.CallTo(() => _userReader.GetCurrentUserName()).ReturnsLazily(() => cUserName); + _usageManager = new SubscriptionUsageManager(); + _subscriptionInfo = new SubscriptionInfo() + { + NumberOfUsers = 10 + }; + } + + [Fact] + public void Should_initialize_new_subscription_usage_data_if_the_provider_returns_null_and_add_current_user() + { + A.CallTo(() => _subscriptionStorageProvider.Read()).ReturnsLazily(() => null); + + var usage = _usageManager.ReadAndUpdateUsage(_subscriptionInfo, _subscriptionStorageProvider, _userReader, false); + + Assert.NotNull(usage); + Assert.Single(usage.Users); + + var userInfo = usage.Users.First(); + Assert.Equal(cUserName, userInfo.UserName); + Assert.True(userInfo.LastTimeUsed > DateTime.UtcNow.AddDays(-1)); + + A.CallTo(() => _subscriptionStorageProvider.Write(usage)) + .MustHaveHappened(); + } + + [Fact] + public void Should_update_last_used_date_if_current_user_already_exists() + { + A.CallTo(() => _subscriptionStorageProvider.Read()).ReturnsLazily(() => new SubscriptionUsage() + { + Users = new List() + { + new SubscriptionUserInfo() + { + UserName = cUserName, + LastTimeUsed = DateTime.UtcNow.AddMonths(-2) + } + } + }); + + var usage = _usageManager.ReadAndUpdateUsage(_subscriptionInfo, _subscriptionStorageProvider, _userReader, false); + + Assert.NotNull(usage); + Assert.Single(usage.Users); + + var userInfo = usage.Users.First(); + Assert.Equal(cUserName, userInfo.UserName); + Assert.True(userInfo.LastTimeUsed > DateTime.UtcNow.AddDays(-1)); + + A.CallTo(() => _subscriptionStorageProvider.Write(usage)) + .MustHaveHappened(); + } + + [Fact] + public void Should_add_upgrade_requested_info_upgrade_requested_and_no_previous_upgrade_info_existed() + { + A.CallTo(() => _subscriptionStorageProvider.Read()).ReturnsLazily(() => null); + + var usage = _usageManager.ReadAndUpdateUsage(_subscriptionInfo, _subscriptionStorageProvider, _userReader, true); + + Assert.NotNull(usage); + Assert.Single(usage.Users); + + var userInfo = usage.Users.First(); + Assert.Equal(cUserName, userInfo.UserName); + Assert.True(userInfo.LastTimeUsed > DateTime.UtcNow.AddDays(-1)); + + var upgradeInfo = usage.UpgradeInfo; + Assert.NotNull(upgradeInfo); + Assert.Equal(_subscriptionInfo.NumberOfUsers, upgradeInfo.PreviousNumberOfUsers); + + A.CallTo(() => _subscriptionStorageProvider.Write(usage)) + .MustHaveHappened(); + } + + [Fact] + public void Should_not_update_upgrade_info_if_upgrade_info_existed_previously() + { + var upgradeDate = DateTime.UtcNow.AddMonths(-1); + + A.CallTo(() => _subscriptionStorageProvider.Read()).ReturnsLazily(() => new SubscriptionUsage() + { + UpgradeInfo = new SubscriptionUpgradeRequest() + { + FirstRequestDate = upgradeDate, + PreviousNumberOfUsers = _subscriptionInfo.NumberOfUsers + }, + Users = new List() + { + new SubscriptionUserInfo() + { + UserName = cUserName, + LastTimeUsed = DateTime.UtcNow.AddMonths(-2) + } + } + }); + + var usage = _usageManager.ReadAndUpdateUsage(_subscriptionInfo, _subscriptionStorageProvider, _userReader, true); + + Assert.NotNull(usage); + Assert.Single(usage.Users); + + var upgradeInfo = usage.UpgradeInfo; + Assert.NotNull(upgradeInfo); + Assert.Equal(_subscriptionInfo.NumberOfUsers, upgradeInfo.PreviousNumberOfUsers); + Assert.Equal(upgradeDate, upgradeInfo.FirstRequestDate); + + A.CallTo(() => _subscriptionStorageProvider.Write(usage)) + .MustHaveHappened(); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.PlanInfo.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.PlanInfo.cs new file mode 100644 index 00000000..ffecb287 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.PlanInfo.cs @@ -0,0 +1,94 @@ +using System; +using FakeItEasy; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public partial class SubscriptionValidatorTests + { + private readonly IEnvironmentReader _defaultEnvironmentReader; + private ISubscriptionInfo _subscriptionInfo; + private SubscriptionValidator _subscriptionValidator; + + + public SubscriptionValidatorTests() + { + _defaultEnvironmentReader = new FakeEnvironmentReader(); + } + + [Fact] + public void Should_return_error_if_current_subscription_is_unknown() + { + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, null, null, false); + Assert.Throws(() => _subscriptionValidator.IsSubscriptionPlanValid()); + } + + [Fact] + public void Should_not_return_subscription_expired_if_still_valid() + { + _subscriptionInfo = new SubscriptionInfo + { + EndDate = DateTime.UtcNow.AddDays(20), + AutoRenews = true + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, null, false); + Assert.True(_subscriptionValidator.IsSubscriptionPlanValid()); + } + + [Fact] + public void Should_not_return_subscription_expired_exception_if_expired_but_autorenew_is_enabled() + { + _subscriptionInfo = new SubscriptionInfo + { + EndDate = DateTime.UtcNow.AddDays(-1), + AutoRenews = true + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, null, false); + Assert.True(_subscriptionValidator.IsSubscriptionPlanValid()); + } + + [Fact] + public void Should_return_subscription_expired_exception_if_expired_with_no_autorenewal() + { + _subscriptionInfo = new SubscriptionInfo + { + EndDate = DateTime.UtcNow.AddDays(-1), + AutoRenews = false + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, null, false); + Assert.Throws(() => _subscriptionValidator.IsSubscriptionPlanValid()); + } + + [Fact] + public void Should_not_return_subscription_expired_exception_if_expired_but_a_renewal_was_requested_within_a_valid_time_frame() + { + _subscriptionInfo = new SubscriptionInfo + { + EndDate = DateTime.UtcNow.AddDays(-15), + AutoRenews = false + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, null, true); + Assert.True(_subscriptionValidator.IsSubscriptionPlanValid()); + } + + [Fact] + public void Should_return_renewal_request_expired_exception_if_expired_and_exceeded_the_valid_time_frame_for_renewal() + { + _subscriptionInfo = new SubscriptionInfo + { + EndDate = DateTime.UtcNow.AddMonths(-1).AddDays(-1), + AutoRenews = false + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, null, true); + Assert.Throws(() => _subscriptionValidator.IsSubscriptionPlanValid()); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.Usage.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.Usage.cs new file mode 100644 index 00000000..7fb67012 --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/SubscriptionValidatorTests.Usage.cs @@ -0,0 +1,149 @@ +using System; +using FakeXrmEasy.Abstractions.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public partial class SubscriptionValidatorTests + { + private ISubscriptionUsage _subscriptionUsage; + + [Fact] + public void Should_return_no_usage_found_exception() + { + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, null, null, false); + Assert.Throws(() => _subscriptionValidator.IsUsageValid()); + } + + [Fact] + public void Should_return_consider_upgrading_exception_if_the_number_of_users_exceeds_the_current_subscription() + { + _subscriptionUsage = new SubscriptionUsage() //3 valid users + { + Users = new SubscriptionUserInfo[] + { + new SubscriptionUserInfo() { UserName = "user1", LastTimeUsed = DateTime.UtcNow.AddDays(-1) }, + new SubscriptionUserInfo() { UserName = "user2", LastTimeUsed = DateTime.UtcNow.AddDays(-10) }, + new SubscriptionUserInfo() { UserName = "user3", LastTimeUsed = DateTime.UtcNow.AddDays(-3) }, + } + }; + _subscriptionInfo = new SubscriptionInfo() + { + NumberOfUsers = 2 + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, _subscriptionUsage, false); + Assert.Throws(() => _subscriptionValidator.IsUsageValid()); + } + + [Fact] + public void Should_not_return_consider_upgrading_exception_if_the_number_of_users_exceeds_the_current_subscription_and_upgrade_was_requested_within_30days() + { + _subscriptionUsage = new SubscriptionUsage() //3 valid users + { + UpgradeInfo = new SubscriptionUpgradeRequest() + { + FirstRequestDate = DateTime.UtcNow.AddMonths(-1).AddDays(1), + PreviousNumberOfUsers = 2 + }, + Users = new SubscriptionUserInfo[] + { + new SubscriptionUserInfo() { UserName = "user1", LastTimeUsed = DateTime.UtcNow.AddDays(-1) }, + new SubscriptionUserInfo() { UserName = "user2", LastTimeUsed = DateTime.UtcNow.AddDays(-10) }, + new SubscriptionUserInfo() { UserName = "user3", LastTimeUsed = DateTime.UtcNow.AddDays(-3) }, + } + }; + _subscriptionInfo = new SubscriptionInfo() + { + NumberOfUsers = 2 + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, _subscriptionUsage, false); + Assert.True(_subscriptionValidator.IsUsageValid()); + } + + [Fact] + public void Should_return_upgrade_request_expired_exception_if_the_number_of_users_exceeds_the_current_subscription_and_upgrade_was_requested_but_took_longer_thab_30days() + { + _subscriptionUsage = new SubscriptionUsage() //3 valid users + { + UpgradeInfo = new SubscriptionUpgradeRequest() + { + FirstRequestDate = DateTime.UtcNow.AddMonths(-1).AddDays(-3), + PreviousNumberOfUsers = 2 + }, + Users = new SubscriptionUserInfo[] + { + new SubscriptionUserInfo() { UserName = "user1", LastTimeUsed = DateTime.UtcNow.AddDays(-1) }, + new SubscriptionUserInfo() { UserName = "user2", LastTimeUsed = DateTime.UtcNow.AddDays(-10) }, + new SubscriptionUserInfo() { UserName = "user3", LastTimeUsed = DateTime.UtcNow.AddDays(-3) }, + } + }; + _subscriptionInfo = new SubscriptionInfo() + { + NumberOfUsers = 2 + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, _subscriptionUsage, false); + Assert.Throws(() => _subscriptionValidator.IsUsageValid()); + } + + + [Fact] + public void Should_not_count_users_where_the_last_time_used_is_greater_than_one_month() + { + _subscriptionUsage = new SubscriptionUsage() //3 valid users + { + Users = new SubscriptionUserInfo[] + { + new SubscriptionUserInfo() { UserName = "user1", LastTimeUsed = DateTime.UtcNow.AddDays(-1) }, + new SubscriptionUserInfo() { UserName = "user2", LastTimeUsed = DateTime.UtcNow.AddMonths(-1).AddDays(-10) }, + new SubscriptionUserInfo() { UserName = "user3", LastTimeUsed = DateTime.UtcNow.AddDays(-3) }, + } + }; + _subscriptionInfo = new SubscriptionInfo() + { + NumberOfUsers = 2 + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, _subscriptionUsage, false); + Assert.True(_subscriptionValidator.IsUsageValid()); + } + + [Fact] + public void Should_return_usage_is_valid_if_it_is_within_the_allowed_range() + { + _subscriptionUsage = new SubscriptionUsage() //3 existing valid users + { + Users = new SubscriptionUserInfo[] + { + new SubscriptionUserInfo() { UserName = "user1", LastTimeUsed = DateTime.UtcNow.AddDays(-1) }, + new SubscriptionUserInfo() { UserName = "user2", LastTimeUsed = DateTime.UtcNow.AddDays(-10) }, + new SubscriptionUserInfo() { UserName = "user3", LastTimeUsed = DateTime.UtcNow.AddDays(-3) }, + } + }; + + _subscriptionInfo = new SubscriptionInfo + { + NumberOfUsers = 3 + }; + + _subscriptionValidator = new SubscriptionValidator(_defaultEnvironmentReader, _subscriptionInfo, _subscriptionUsage, false); + Assert.True(_subscriptionValidator.IsUsageValid()); + } + + [Theory] + [InlineData("FAKE_XRM_EASY_CI", "1")] + [InlineData("TF_BUILD", "True")] + public void Should_ignore_usage_if_running_inside_ci(string envVariableName, string envVariableValue) + { + var continuousIntegrationEnvironmentReader = new FakeEnvironmentReader(); + continuousIntegrationEnvironmentReader.SetEnvironmentVariable(envVariableName, envVariableValue); + + _subscriptionValidator = new SubscriptionValidator(continuousIntegrationEnvironmentReader, null, _subscriptionUsage, false); + Assert.True(_subscriptionValidator.IsUsageValid()); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/CommercialLicense/UserReaderTests.cs b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/UserReaderTests.cs new file mode 100644 index 00000000..883c4e1f --- /dev/null +++ b/tests/FakeXrmEasy.Core.Tests/CommercialLicense/UserReaderTests.cs @@ -0,0 +1,22 @@ +using System; +using FakeXrmEasy.Core.CommercialLicense; +using Xunit; + +namespace FakeXrmEasy.Core.Tests.CommercialLicense +{ + public class UserReaderTests + { + private readonly UserReader _userReader; + + public UserReaderTests() + { + _userReader = new UserReader(); + } + + [Fact] + public void Should_return_current_user() + { + Assert.Equal(Environment.UserName, _userReader.GetCurrentUserName()); + } + } +} \ No newline at end of file diff --git a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj index d5692389..55f0235a 100644 --- a/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj +++ b/tests/FakeXrmEasy.Core.Tests/FakeXrmEasy.Core.Tests.csproj @@ -11,7 +11,7 @@ true FakeXrmEasy.CoreTests - 3.3.3 + 3.4.0 Jordi Montaña Dynamics Value S.L. Internal Unit test suite for FakeXrmEasy.Core package @@ -82,25 +82,25 @@ - + - + - + - + - + - + - + @@ -108,22 +108,22 @@ - + - + - + - + - + - + diff --git a/tests/FakeXrmEasy.Core.Tests/Middleware/MiddlewareBuilderTests.cs b/tests/FakeXrmEasy.Core.Tests/Middleware/MiddlewareBuilderTests.cs index 3f7b01d3..2df5de5a 100644 --- a/tests/FakeXrmEasy.Core.Tests/Middleware/MiddlewareBuilderTests.cs +++ b/tests/FakeXrmEasy.Core.Tests/Middleware/MiddlewareBuilderTests.cs @@ -6,13 +6,36 @@ using Crm; using FakeXrmEasy.Abstractions.Middleware; using FakeXrmEasy.Abstractions; +using FakeXrmEasy.Abstractions.CommercialLicense; using FakeXrmEasy.Abstractions.Enums; using FakeXrmEasy.Abstractions.Exceptions; +using FakeXrmEasy.Core.CommercialLicense; +using FakeXrmEasy.Core.CommercialLicense.Exceptions; namespace FakeXrmEasy.Core.Tests.Middleware { - public partial class MiddlewareBuilderTests + public partial class MiddlewareBuilderTests { + private readonly ISubscriptionStorageProvider _fakeSubscriptionStorageProvider; + private readonly ISubscriptionInfo _subscriptionInfo; + private readonly ISubscriptionUsage _subscriptionUsage; + public MiddlewareBuilderTests() + { + _subscriptionInfo = new SubscriptionInfo() + { + EndDate = DateTime.UtcNow.AddDays(1), + NumberOfUsers = 1 + }; + + _subscriptionUsage = new SubscriptionUsage() + { + + }; + + _fakeSubscriptionStorageProvider = A.Fake(); + A.CallTo(() => _fakeSubscriptionStorageProvider.Read()).ReturnsLazily(() => _subscriptionUsage); + } + [Fact] public void Should_create_new_instance() { @@ -121,7 +144,21 @@ public void Should_throw_exception_when_building_without_a_license() { Assert.Throws(() => MiddlewareBuilder.New().Build()); } + + [Fact] + public void Should_not_throw_exception_when_using_commercial_license_with_custom_storage_and_valid_data() + { + SubscriptionManager._subscriptionInfo = _subscriptionInfo; + SubscriptionManager._subscriptionUsage = _subscriptionUsage; + var ctx = MiddlewareBuilder + .New() + .SetLicense(FakeXrmEasyLicense.Commercial) + .Build(); + + Assert.Equal(FakeXrmEasyLicense.Commercial, ctx.LicenseContext); + } + [Fact] public void Should_throw_exception_when_using_default_faked_context_constructor_without_a_license() { @@ -138,6 +175,7 @@ public void Should_not_throw_exception_when_using_default_faked_context_construc #pragma warning restore CS0618 // Type or member is obsolete Assert.Null(exception); } + }