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);
}
+
}