diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ddf5f63e..6cb145e9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,8 @@ "Bash(cat:*)", "Bash(dotnet build:*)", "Bash(dotnet test:*)", - "Bash(dotnet pack:*)" + "Bash(dotnet pack:*)", + "Bash(find:*)" ] } } diff --git a/.gitignore b/.gitignore index 7a89f14f..f6d83a36 100644 --- a/.gitignore +++ b/.gitignore @@ -207,3 +207,6 @@ docs/tools/FSharp.Formatting.svclog /XrmMockup/tools/Signature/delegate.snk /tools/XrmContext/EnvInfo.config /src/MetadataGen/XrmMockupTest.csproj + +# Claude temp files +tmpclaude-*-cwd diff --git a/Tools/Tools.csproj b/Tools/Tools.csproj index d3e29fe4..ab766e06 100644 --- a/Tools/Tools.csproj +++ b/Tools/Tools.csproj @@ -64,7 +64,7 @@ True - ..\packages\Microsoft.CrmSdk.Workflow.9.0.2.23\lib\net462\Microsoft.Xrm.Sdk.Workflow.dll + ..\packages\Microsoft.CrmSdk.Workflow.9.0.2.60\lib\net462\Microsoft.Xrm.Sdk.Workflow.dll True diff --git a/Tools/packages.config b/Tools/packages.config index c42adad0..135f44eb 100644 --- a/Tools/packages.config +++ b/Tools/packages.config @@ -5,7 +5,7 @@ - + diff --git a/XrmMockup.slnx b/XrmMockup.slnx index 60946edd..d88d98c5 100644 --- a/XrmMockup.slnx +++ b/XrmMockup.slnx @@ -25,6 +25,8 @@ + + diff --git a/scripts/Pack-Local.ps1 b/scripts/Pack-Local.ps1 index 7ebaa44d..4f336378 100644 --- a/scripts/Pack-Local.ps1 +++ b/scripts/Pack-Local.ps1 @@ -1,3 +1,7 @@ +param( + [string]$Output = "./nupkg" +) + # Local pack script for XrmMockup packages # Sets versions from changelogs and creates NuGet packages locally @@ -13,5 +17,5 @@ dotnet build --configuration Release # Pack specific projects (not the entire solution to avoid legacy project errors) -dotnet pack ./src/XrmMockup365/XrmMockup365.csproj --configuration Release --no-build --output ./nupkg -dotnet pack ./src/MetadataGen/MetadataGenerator.Tool/MetadataGenerator.Tool.csproj --configuration Release --no-build --output ./nupkg +dotnet pack ./src/XrmMockup365/XrmMockup365.csproj --configuration Release --no-build --output $Output +dotnet pack ./src/MetadataGen/MetadataGenerator.Tool/MetadataGenerator.Tool.csproj --configuration Release --no-build --output $Output diff --git a/src/MetadataGen/MetadataGenerator.Tool.Tests/MetadataGenerator.Tool.Tests.csproj b/src/MetadataGen/MetadataGenerator.Tool.Tests/MetadataGenerator.Tool.Tests.csproj index a35d53e9..18317d0e 100644 --- a/src/MetadataGen/MetadataGenerator.Tool.Tests/MetadataGenerator.Tool.Tests.csproj +++ b/src/MetadataGen/MetadataGenerator.Tool.Tests/MetadataGenerator.Tool.Tests.csproj @@ -20,7 +20,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/XrmMockup365/Core.cs b/src/XrmMockup365/Core.cs index 8fe3d26d..a5b8df16 100644 --- a/src/XrmMockup365/Core.cs +++ b/src/XrmMockup365/Core.cs @@ -1,10 +1,10 @@ -using DG.Tools.XrmMockup.Database; +using DG.Tools.XrmMockup.Database; using DG.Tools.XrmMockup.Internal; using DG.Tools.XrmMockup.Serialization; +using DG.Tools.XrmMockup.Online; using XrmPluginCore.Enums; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Client; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Organization; @@ -59,7 +59,7 @@ internal class Core : IXrmMockupExtension private XrmDb db; private Dictionary snapshots; private Dictionary entityTypeMap = new Dictionary(); - private OrganizationServiceProxy OnlineProxy; + private IOnlineDataService OnlineDataService; private int baseCurrencyPrecision; private FormulaFieldEvaluator FormulaFieldEvaluator { get; set; } private List systemAttributeNames; @@ -79,7 +79,7 @@ public Core(XrmMockupSettings Settings, MetadataSkeleton metadata, List SecurityRoles = SecurityRoles, BaseCurrency = metadata.BaseOrganization.GetAttributeValue("basecurrencyid"), BaseCurrencyPrecision = metadata.BaseOrganization.GetAttributeValue("pricingdecimalprecision"), - OnlineProxy = null, + OnlineDataService = null, EntityTypeMap = new Dictionary() }; @@ -99,7 +99,7 @@ public Core(XrmMockupSettings Settings, StaticMetadataCache staticCache) SecurityRoles = staticCache.SecurityRoles, BaseCurrency = staticCache.BaseCurrency, BaseCurrencyPrecision = staticCache.BaseCurrencyPrecision, - OnlineProxy = staticCache.OnlineProxy, + OnlineDataService = staticCache.OnlineDataService, EntityTypeMap = staticCache.EntityTypeMap }; @@ -116,10 +116,9 @@ private void InitializeCore(CoreInitializationData initData) metadata = initData.Metadata; BaseCurrency = initData.BaseCurrency; baseCurrencyPrecision = initData.BaseCurrencyPrecision; - OnlineProxy = initData.OnlineProxy; + OnlineDataService = initData.OnlineDataService; + db = new XrmDb(initData.Metadata.EntityMetadata, initData.OnlineDataService); entityTypeMap = initData.EntityTypeMap; - - db = new XrmDb(initData.Metadata.EntityMetadata, initData.OnlineProxy); EnsureFileAttachmentMetadata(); FileBlockStore = new FileBlockStore(); snapshots = new Dictionary(); @@ -185,7 +184,7 @@ public static StaticMetadataCache BuildStaticMetadataCache(XrmMockupSettings set var baseCurrency = metadata.BaseOrganization.GetAttributeValue("basecurrencyid"); var baseCurrencyPrecision = metadata.BaseOrganization.GetAttributeValue("pricingdecimalprecision"); - var onlineProxy = BuildOnlineProxy(settings); + var onlineDataService = BuildOnlineDataService(settings); var entityTypeMap = new Dictionary(); // Build entity type map for proxy types if enabled @@ -194,29 +193,28 @@ public static StaticMetadataCache BuildStaticMetadataCache(XrmMockupSettings set BuildEntityTypeMap(settings, entityTypeMap); } - // Note: IPluginMetadata is handled per-instance in the Core constructor + // Note: IPluginMetadata is handled per-instance in the Core constructor // to avoid modifying the shared cache - return new StaticMetadataCache(metadata, workflows, securityRoles, entityTypeMap, - baseCurrency, baseCurrencyPrecision, onlineProxy); + return new StaticMetadataCache(metadata, workflows, securityRoles, entityTypeMap, + baseCurrency, baseCurrencyPrecision, onlineDataService); } - private static OrganizationServiceProxy BuildOnlineProxy(XrmMockupSettings settings) + private static IOnlineDataService BuildOnlineDataService(XrmMockupSettings settings) { +#if DATAVERSE_SERVICE_CLIENT + // Allow injection for testing + if (settings.OnlineDataServiceFactory != null) + { + return settings.OnlineDataServiceFactory(); + } + if (settings.OnlineEnvironment.HasValue) { var env = settings.OnlineEnvironment.Value; - var orgHelper = new OrganizationHelper( - new Uri(env.uri), - env.providerType, - env.username, - env.password, - env.domain); - var proxy = orgHelper.GetServiceProxy(); - if (settings.EnableProxyTypes == true) - proxy.EnableProxyTypes(); - return proxy; + return new OnlineDataService(env.Url); } +#endif return null; } @@ -427,25 +425,6 @@ internal void EnableProxyTypes(Assembly assembly) } } - private OrganizationServiceProxy GetOnlineProxy() - { - if (OnlineProxy == null && settings.OnlineEnvironment.HasValue) - { - var env = settings.OnlineEnvironment.Value; - var orgHelper = new OrganizationHelper( - new Uri(env.uri), - env.providerType, - env.username, - env.password, - env.domain); - this.OnlineProxy = orgHelper.GetServiceProxy(); - if (settings.EnableProxyTypes == true) - OnlineProxy.EnableProxyTypes(); - } - - return OnlineProxy; - } - internal IOrganizationService GetWorkflowService() { return ServiceFactory.CreateOrganizationService(null, @@ -1121,6 +1100,14 @@ internal void PopulateWith(Entity[] entities) } } + /// + /// Prefills the local database with data from the online service based on the query. + /// + internal void PrefillDBWithOnlineData(QueryExpression query) + { + db.PrefillDBWithOnlineData(query); + } + internal Dictionary> GetPrivilege(Guid principleId) { return security.GetPrincipalPrivilege(principleId); @@ -1356,7 +1343,7 @@ internal void ResetEnvironment() workflowManager.ResetWorkflows(settings.IncludeAllWorkflows); pluginManager.ResetPlugins(); - this.db = new XrmDb(metadata.EntityMetadata, GetOnlineProxy()); + this.db = new XrmDb(metadata.EntityMetadata, OnlineDataService); EnsureFileAttachmentMetadata(); this.RequestHandlers = GetRequestHandlers(db); InitializeDB(); diff --git a/src/XrmMockup365/Database/DeviceIdManager.cs b/src/XrmMockup365/Database/DeviceIdManager.cs deleted file mode 100644 index d3e340ce..00000000 --- a/src/XrmMockup365/Database/DeviceIdManager.cs +++ /dev/null @@ -1,871 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace DG.Tools.XrmMockup -{ - // ==================================================================== - // - // This file is part of the Microsoft Dynamics CRM SDK code samples. - // - // Copyright (C) Microsoft Corporation. All rights reserved. - // - // This source code is intended only as a supplement to Microsoft - // Development Tools and/or on-line documentation. See these other - // materials for detailed information regarding Microsoft code samples. - // - // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY - // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A - // PARTICULAR PURPOSE. - // - // ==================================================================== - // - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Net; - using System.Runtime.Serialization; - using System.Security.Cryptography; - using System.ServiceModel.Description; - using System.Text; - using System.Xml; - using System.Xml.Serialization; - - namespace Microsoft.Crm.Services.Utility { - /// - /// Management utility for the Device Id - /// - public static class DeviceIdManager { - #region Fields - private static readonly Random RandomInstance = new Random(); - - public const int MaxDeviceNameLength = 24; - public const int MaxDevicePasswordLength = 24; - #endregion - - #region Constructor - static DeviceIdManager() { - PersistToFile = true; - } - #endregion - - #region Properties - /// - /// Indicates whether the registered device credentials should be persisted to the database - /// - public static bool PersistToFile { get; set; } - - /// - /// Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists. - /// - /// - /// If the device already exists, there is a possibility that the credentials are the same as the current credentials that - /// are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid - /// registering spurious device credentials. - /// - public static bool PersistIfDeviceAlreadyExists { get; set; } - #endregion - - #region Methods - /// - /// Loads the device credentials (if they exist). - /// - /// - public static ClientCredentials LoadOrRegisterDevice() { - return LoadOrRegisterDevice(null); - } - - /// - /// Loads the device credentials (if they exist). - /// - /// Device name that should be registered - /// Device password that should be registered - public static ClientCredentials LoadOrRegisterDevice(string deviceName, string devicePassword) { - return LoadOrRegisterDevice(null, deviceName, devicePassword); - } - - /// - /// Loads the device credentials (if they exist). - /// - /// URL for the current token issuer - /// - /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. - /// - public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri) { - return LoadOrRegisterDevice(issuerUri, null, null); - } - - /// - /// Loads the device credentials (if they exist). - /// - /// URL for the current token issuer - /// Device name that should be registered - /// Device password that should be registered - /// - /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. - /// - public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri, string deviceName, string devicePassword) { - ClientCredentials credentials = LoadDeviceCredentials(issuerUri); - if (null == credentials) { - credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword); - } - - return credentials; - } - - /// - /// Registers the given device with Microsoft account with a random application ID - /// - /// ClientCredentials that were registered - public static ClientCredentials RegisterDevice() { - return RegisterDevice(Guid.NewGuid()); - } - - /// - /// Registers the given device with Microsoft account - /// - /// ID for the application - /// ClientCredentials that were registered - public static ClientCredentials RegisterDevice(Guid applicationId) { - return RegisterDevice(applicationId, (Uri)null); - } - - /// - /// Registers the given device with Microsoft account - /// - /// ID for the application - /// URL for the current token issuer - /// ClientCredentials that were registered - /// - /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. - /// - public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri) { - return RegisterDevice(applicationId, issuerUri, null, null); - } - - /// - /// Registers the given device with Microsoft account - /// - /// ID for the application - /// Device name that should be registered - /// Device password that should be registered - /// ClientCredentials that were registered - public static ClientCredentials RegisterDevice(Guid applicationId, string deviceName, string devicePassword) { - return RegisterDevice(applicationId, (Uri)null, deviceName, devicePassword); - } - - /// - /// Registers the given device with Microsoft account - /// - /// ID for the application - /// URL for the current token issuer - /// Device name that should be registered - /// Device password that should be registered - /// ClientCredentials that were registered - /// - /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. - /// - public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, string deviceName, string devicePassword) { - if (string.IsNullOrEmpty(deviceName) && !PersistToFile) { - throw new ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified."); - } else if (string.IsNullOrEmpty(deviceName) != string.IsNullOrEmpty(devicePassword)) { - throw new ArgumentNullException("deviceName", "Either deviceName/devicePassword should both be specified or they should be null."); - } - - LiveDevice device = GenerateDevice(deviceName, devicePassword); - return RegisterDevice(applicationId, issuerUri, device); - } - - /// - /// Loads the device's credentials from the file system - /// - /// Device Credentials (if set) or null - public static ClientCredentials LoadDeviceCredentials() { - return LoadDeviceCredentials(null); - } - - /// - /// Loads the device's credentials from the file system - /// - /// URL for the current token issuer - /// Device Credentials (if set) or null - /// - /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. - /// - public static ClientCredentials LoadDeviceCredentials(Uri issuerUri) { - //If the credentials should not be persisted to a file, then they won't be present on the disk. - if (!PersistToFile) { - return null; - } - - EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri); - - LiveDevice device = ReadExistingDevice(environment); - if (null == device || null == device.User) { - return null; - } - - return device.User.ToClientCredentials(); - } - - /// - /// Discovers the Microsoft account environment based on the Token Issuer - /// - public static string DiscoverEnvironment(Uri issuerUri) { - return DiscoverEnvironmentInternal(issuerUri).Environment; - } - #endregion - - #region Private Methods - private static EnvironmentConfiguration DiscoverEnvironmentInternal(Uri issuerUri) { - if (null == issuerUri) { - return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", null); - } - - Dictionary searchList = new Dictionary(); - searchList.Add(EnvironmentType.LiveDeviceID, "login.live"); - searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline"); - - foreach (KeyValuePair searchPair in searchList) { - if (issuerUri.Host.Length > searchPair.Value.Length && - issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase)) { - string environment = issuerUri.Host.Substring(searchPair.Value.Length); - - //Parse out the environment - if ('-' == environment[0]) { - int separatorIndex = environment.IndexOf('.', 1); - if (-1 != separatorIndex) { - environment = environment.Substring(1, separatorIndex - 1); - } else { - environment = null; - } - } else { - environment = null; - } - - return new EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment); - } - } - - //In all other cases the environment is either not applicable or it is a production system - return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, null); - } - - private static void Serialize(Stream stream, T value) { - XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); - - XmlSerializerNamespaces xmlNamespaces = new XmlSerializerNamespaces(); - xmlNamespaces.Add(string.Empty, string.Empty); - - serializer.Serialize(stream, value, xmlNamespaces); - } - - private static T Deserialize(string operationName, Stream stream) { - //Read the XML into memory so that the data can be used in an exception if necessary - using (StreamReader reader = new StreamReader(stream)) { - return Deserialize(operationName, reader.ReadToEnd()); - } - } - - private static T Deserialize(string operationName, string xml) { - //Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further - //investigation - using (StringReader reader = new StringReader(xml)) { - try { - XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); - return (T)serializer.Deserialize(reader); - } catch (InvalidOperationException ex) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex); - } - } - } - - private static FileInfo GetDeviceFile(EnvironmentConfiguration environment) { - return new FileInfo(string.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat, - environment.Type, - string.IsNullOrEmpty(environment.Environment) ? null : "-" + environment.Environment.ToUpperInvariant())); - } - - private static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, LiveDevice device) { - EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri); - - DeviceRegistrationRequest request = new DeviceRegistrationRequest(applicationId, device); - - string url = string.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat, - environment.HostName); - - DeviceRegistrationResponse response = ExecuteRegistrationRequest(url, request); - if (!response.IsSuccess) { - bool throwException = true; - if (DeviceRegistrationErrorCode.DeviceAlreadyExists == response.Error.RegistrationErrorCode) { - if (!PersistToFile) { - //If the file is not persisted, the registration will always occur (since the credentials are not - //persisted to the disk. However, the credentials may already exist. To avoid an exception being continually - //processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk. - return device.User.ToClientCredentials(); - } else if (PersistIfDeviceAlreadyExists) { - // This flag indicates that the - throwException = false; - } - } - - if (throwException) { - throw new DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode); - } - } - - if (PersistToFile || PersistIfDeviceAlreadyExists) { - WriteDevice(environment, device); - } - - return device.User.ToClientCredentials(); - } - - private static LiveDevice GenerateDevice(string deviceName, string devicePassword) { - // If the deviceName hasn't been specified, it should be generated using random characters. - DeviceUserName userNameCredentials; - if (string.IsNullOrEmpty(deviceName)) { - userNameCredentials = GenerateDeviceUserName(); - } else { - userNameCredentials = new DeviceUserName() { DeviceName = deviceName, DecryptedPassword = devicePassword }; - } - - return new LiveDevice() { User = userNameCredentials, Version = 1 }; - } - - private static LiveDevice ReadExistingDevice(EnvironmentConfiguration environment) { - //Retrieve the file info - FileInfo file = GetDeviceFile(environment); - if (!file.Exists) { - return null; - } - - using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { - return Deserialize("Loading Device Credentials from Disk", stream); - } - } - - private static void WriteDevice(EnvironmentConfiguration environment, LiveDevice device) { - FileInfo file = GetDeviceFile(environment); - if (!file.Directory.Exists) { - file.Directory.Create(); - } - - using (FileStream stream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None)) { - Serialize(stream, device); - } - } - - private static DeviceRegistrationResponse ExecuteRegistrationRequest(string url, DeviceRegistrationRequest registrationRequest) { - //Create the request that will submit the request to the server - WebRequest request = WebRequest.Create(url); - request.ContentType = "application/soap+xml; charset=UTF-8"; - request.Method = "POST"; - request.Timeout = 180000; - - //Write the envelope to the RequestStream - using (Stream stream = request.GetRequestStream()) { - Serialize(stream, registrationRequest); - } - - // Read the response into an XmlDocument and return that doc - try { - using (WebResponse response = request.GetResponse()) { - using (Stream stream = response.GetResponseStream()) { - return Deserialize("Deserializing Registration Response", stream); - } - } - } catch (WebException ex) { - System.Diagnostics.Trace.TraceError("Microsoft account Device Registration Failed (HTTP Code: {0}): {1}", - ex.Status, ex.Message); - - if (null != ex.Response) { - using (Stream stream = ex.Response.GetResponseStream()) { - return Deserialize("Deserializing Failed Registration Response", stream); - } - } - - throw; - } - } - - private static DeviceUserName GenerateDeviceUserName() { - DeviceUserName userName = new DeviceUserName(); - userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength); - userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength); - - return userName; - } - - private static string GenerateRandomString(string characterSet, int count) { - //Create an array of the characters that will hold the final list of random characters - char[] value = new char[count]; - - //Convert the character set to an array that can be randomly accessed - char[] set = characterSet.ToCharArray(); - - lock (RandomInstance) { - //Populate the array with random characters from the character set - for (int i = 0; i < count; i++) { - value[i] = set[RandomInstance.Next(0, set.Length)]; - } - } - - return new string(value); - } - #endregion - - #region Private Classes - private enum EnvironmentType { - LiveDeviceID, - OrgDeviceID - } - - private sealed class EnvironmentConfiguration { - public EnvironmentConfiguration(EnvironmentType type, string hostName, string environment) { - if (string.IsNullOrWhiteSpace(hostName)) { - throw new ArgumentNullException("hostName"); - } - - this.Type = type; - this.HostName = hostName; - this.Environment = environment; - } - - #region Properties - public EnvironmentType Type { get; private set; } - - public string HostName { get; private set; } - - public string Environment { get; private set; } - #endregion - } - - private static class LiveIdConstants { - public const string RegistrationEndpointUriFormat = @"https://{0}/ppsecure/DeviceAddCredential.srf"; - - public static readonly string FileNameFormat = Path.Combine( - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"), - "{0}{1}.xml"); - - public const string ValidDeviceNameCharacters = "0123456789abcdefghijklmnopqrstuvqxyz"; - - //Consists of the list of characters specified in the documentation - public const string ValidDevicePasswordCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~"; - } - #endregion - } - - #region Public Classes & Enums - /// - /// Indicates an error during registration - /// - public enum DeviceRegistrationErrorCode { - /// - /// Unspecified or Unknown Error occurred - /// - Unknown = 0, - - /// - /// Interface Disabled - /// - InterfaceDisabled = 1, - - /// - /// Invalid Request Format - /// - InvalidRequestFormat = 3, - - /// - /// Unknown Client Version - /// - UnknownClientVersion = 4, - - /// - /// Blank Password - /// - BlankPassword = 6, - - /// - /// Missing Device User Name or Password - /// - MissingDeviceUserNameOrPassword = 7, - - /// - /// Invalid Parameter Syntax - /// - InvalidParameterSyntax = 8, - - /// - /// Invalid Characters are used in the device credentials. - /// - InvalidCharactersInCredentials = 9, - - /// - /// Internal Error - /// - InternalError = 11, - - /// - /// Device Already Exists - /// - DeviceAlreadyExists = 13 - } - - /// - /// Indicates that Device Registration failed - /// - [Serializable] - public sealed class DeviceRegistrationFailedException : Exception { - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - public DeviceRegistrationFailedException() - : base() { - } - - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - /// Message to pass - public DeviceRegistrationFailedException(string message) - : base(message) { - } - - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - /// Message to pass - /// Exception to include - public DeviceRegistrationFailedException(string message, Exception innerException) - : base(message, innerException) { - } - - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - /// Error code that occurred - /// Subcode that occurred - public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode) - : this(code, subCode, null) { - } - - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - /// Error code that occurred - /// Subcode that occurred - /// Inner exception - public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode, Exception innerException) - : base(string.Concat(code.ToString(), ": ", subCode), innerException) { - this.RegistrationErrorCode = code; - } - - /// - /// Construct an instance of the DeviceRegistrationFailedException class - /// - /// - /// - private DeviceRegistrationFailedException(SerializationInfo si, StreamingContext sc) - : base(si, sc) { - } - - #region Properties - /// - /// Error code that occurred during registration - /// - public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; } - #endregion - - #region Methods - public override void GetObjectData(SerializationInfo info, StreamingContext context) { - base.GetObjectData(info, context); - } - #endregion - } - - #region Serialization Classes - #region DeviceRegistrationRequest Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("DeviceAddRequest")] - public sealed class DeviceRegistrationRequest { - #region Constructors - public DeviceRegistrationRequest() { - } - - public DeviceRegistrationRequest(Guid applicationId, LiveDevice device) - : this() { - if (null == device) { - throw new ArgumentNullException("device"); - } - - this.ClientInfo = new DeviceRegistrationClientInfo() { ApplicationId = applicationId, Version = "1.0" }; - this.Authentication = new DeviceRegistrationAuthentication() { - MemberName = device.User.DeviceId, - Password = device.User.DecryptedPassword - }; - } - #endregion - - #region Properties - [XmlElement("ClientInfo")] - public DeviceRegistrationClientInfo ClientInfo { get; set; } - - [XmlElement("Authentication")] - public DeviceRegistrationAuthentication Authentication { get; set; } - #endregion - } - #endregion - - #region DeviceRegistrationClientInfo Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("ClientInfo")] - public sealed class DeviceRegistrationClientInfo { - #region Properties - [XmlAttribute("name")] - public Guid ApplicationId { get; set; } - - [XmlAttribute("version")] - public string Version { get; set; } - #endregion - } - #endregion - - #region DeviceRegistrationAuthentication Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("Authentication")] - public sealed class DeviceRegistrationAuthentication { - #region Properties - [XmlElement("Membername")] - public string MemberName { get; set; } - - [XmlElement("Password")] - public string Password { get; set; } - #endregion - } - #endregion - - #region DeviceRegistrationResponse Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("DeviceAddResponse")] - public sealed class DeviceRegistrationResponse { - #region Properties - [XmlElement("success")] - public bool IsSuccess { get; set; } - - [XmlElement("puid")] - public string Puid { get; set; } - - [XmlElement("Error")] - public DeviceRegistrationResponseError Error { get; set; } - - [XmlElement("ErrorSubcode")] - public string ErrorSubCode { get; set; } - #endregion - } - #endregion - - #region DeviceRegistrationResponse Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("Error")] - public sealed class DeviceRegistrationResponseError { - private string _code; - - #region Properties - [XmlAttribute("Code")] - public string Code { - get { - return this._code; - } - - set { - this._code = value; - - //Parse the error code - if (!string.IsNullOrEmpty(value)) { - //Parse the error code - if (value.StartsWith("dc", StringComparison.Ordinal)) { - int code; - if (int.TryParse(value.Substring(2), NumberStyles.Integer, - CultureInfo.InvariantCulture, out code) && - Enum.IsDefined(typeof(DeviceRegistrationErrorCode), code)) { - this.RegistrationErrorCode = (DeviceRegistrationErrorCode)Enum.ToObject( - typeof(DeviceRegistrationErrorCode), code); - } - } - } - } - } - - [XmlIgnore] - public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; } - #endregion - } - #endregion - - #region LiveDevice Class - [EditorBrowsable(EditorBrowsableState.Never)] - [XmlRoot("Data")] - public sealed class LiveDevice { - #region Properties - [XmlAttribute("version")] - public int Version { get; set; } - - [XmlElement("User")] - public DeviceUserName User { get; set; } - - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "This is required for proper XML Serialization")] - [XmlElement("Token")] - public XmlNode Token { get; set; } - - [XmlElement("Expiry")] - public string Expiry { get; set; } - - [XmlElement("ClockSkew")] - public string ClockSkew { get; set; } - #endregion - } - #endregion - - #region DeviceUserName Class - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class DeviceUserName { - private string _encryptedPassword; - private string _decryptedPassword; - private bool _encryptedValueIsUpdated; - - #region Constants - private const string UserNamePrefix = "11"; - #endregion - - #region Constructors - public DeviceUserName() { - this.UserNameType = "Logical"; - } - #endregion - - #region Properties - [XmlAttribute("username")] - public string DeviceName { get; set; } - - [XmlAttribute("type")] - public string UserNameType { get; set; } - - [XmlElement("Pwd")] - public string EncryptedPassword { - get { - this.ThrowIfNoEncryption(); - - if (!this._encryptedValueIsUpdated) { - this._encryptedPassword = this.Encrypt(this._decryptedPassword); - this._encryptedValueIsUpdated = true; - } - - return this._encryptedPassword; - } - - set { - this.ThrowIfNoEncryption(); - this.UpdateCredentials(value, null); - } - } - - public string DeviceId { - get { - return UserNamePrefix + DeviceName; - } - } - - [XmlIgnore] - public string DecryptedPassword { - get { - return this._decryptedPassword; - } - - set { - this.UpdateCredentials(null, value); - } - } - - private bool IsEncryptionEnabled { - get { - //If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra - //overhead and will not function in partial trust. - return DeviceIdManager.PersistToFile; - } - } - #endregion - - #region Methods - public ClientCredentials ToClientCredentials() { - ClientCredentials credentials = new ClientCredentials(); - credentials.UserName.UserName = this.DeviceId; - credentials.UserName.Password = this.DecryptedPassword; - - return credentials; - } - - private void ThrowIfNoEncryption() { - if (!this.IsEncryptionEnabled) { - throw new NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false."); - } - } - - private void UpdateCredentials(string encryptedValue, string decryptedValue) { - bool isValueUpdated = false; - if (string.IsNullOrEmpty(encryptedValue) && string.IsNullOrEmpty(decryptedValue)) { - isValueUpdated = true; - } else if (string.IsNullOrEmpty(encryptedValue)) { - if (this.IsEncryptionEnabled) { - encryptedValue = this.Encrypt(decryptedValue); - isValueUpdated = true; - } else { - encryptedValue = null; - isValueUpdated = false; - } - } else { - this.ThrowIfNoEncryption(); - - decryptedValue = this.Decrypt(encryptedValue); - isValueUpdated = true; - } - - this._encryptedPassword = encryptedValue; - this._decryptedPassword = decryptedValue; - this._encryptedValueIsUpdated = isValueUpdated; - } - - private string Encrypt(string value) { - if (string.IsNullOrEmpty(value)) { - return value; - } - - byte[] encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), null, DataProtectionScope.CurrentUser); - return Convert.ToBase64String(encryptedBytes); - } - - private string Decrypt(string value) { - if (string.IsNullOrEmpty(value)) { - return value; - } - - byte[] decryptedBytes = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser); - if (null == decryptedBytes || 0 == decryptedBytes.Length) { - return null; - } - - return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length); - } - #endregion - } - #endregion - #endregion - #endregion - } - // -} diff --git a/src/XrmMockup365/Database/OrganizationHelper.cs b/src/XrmMockup365/Database/OrganizationHelper.cs deleted file mode 100644 index 3d3c9d96..00000000 --- a/src/XrmMockup365/Database/OrganizationHelper.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Client; -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Text; - -namespace DG.Tools.XrmMockup { - public class OrganizationHelper { - - private Uri _uri; - private AuthenticationProviderType _ap; - private string _userName; - private string _password; - private string _domain; - - public OrganizationHelper() { - _uri = new Uri(GetConnectionString("CrmUri") ?? ""); - _ap = (AuthenticationProviderType)Enum.Parse(typeof(AuthenticationProviderType), - GetConnectionString("CrmAp")); - _userName = GetConnectionString("CrmUsr"); - _password = GetConnectionString("CrmPwd"); - _domain = GetConnectionString("CrmDmn"); - } - - public OrganizationHelper(Uri uri, AuthenticationProviderType ap, string userName, string password, string domain = null) { - _uri = uri; - _ap = ap; - _userName = userName; - _password = password; - _domain = domain; - } - - private string GetConnectionString(string name) { - return ConfigurationManager.ConnectionStrings[name]?.ConnectionString; - } - - public OrganizationServiceProxy GetServiceProxy() { - var proxy = GetServiceProxyInternal(); -#if DATAVERSE_SERVICE_CLIENT - proxy.ServiceConfiguration.CurrentServiceEndpoint.EndpointBehaviors.Add(new ProxyTypesBehavior()); -#else - proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior()); -#endif - proxy.Timeout = new TimeSpan(1, 0, 0); - return proxy; - } - - private OrganizationServiceProxy GetServiceProxyInternal() { - var management = ServiceConfigurationFactory.CreateManagement(_uri); - var ac = management.Authenticate(GetCredentials(management, _ap)); - - switch (_ap) { - case AuthenticationProviderType.ActiveDirectory: - return new OrganizationServiceProxy(management, ac.ClientCredentials); - default: - return new OrganizationServiceProxy(management, ac.SecurityTokenResponse); - } - } - - private AuthenticationCredentials GetCredentials(IServiceManagement service, AuthenticationProviderType endpointType) { - AuthenticationCredentials authCredentials = new AuthenticationCredentials(); - - switch (endpointType) { - case AuthenticationProviderType.ActiveDirectory: - authCredentials.ClientCredentials.Windows.ClientCredential = - new System.Net.NetworkCredential(_userName, _password, _domain); - break; - case AuthenticationProviderType.LiveId: - authCredentials.ClientCredentials.UserName.UserName = _userName; - authCredentials.ClientCredentials.UserName.Password = _password; - authCredentials.SupportingCredentials = new AuthenticationCredentials(); - authCredentials.SupportingCredentials.ClientCredentials = - Microsoft.Crm.Services.Utility.DeviceIdManager.LoadOrRegisterDevice(); - break; - default: // For Federated and OnlineFederated environments. - authCredentials.ClientCredentials.UserName.UserName = _userName; - authCredentials.ClientCredentials.UserName.Password = _password; - // For OnlineFederated single-sign on, you could just use current UserPrincipalName instead of passing user name and password. - // authCredentials.UserPrincipalName = UserPrincipal.Current.UserPrincipalName; // Windows Kerberos - - // The service is configured for User Id authentication, but the user might provide Microsoft - // account credentials. If so, the supporting credentials must contain the device credentials. - if (endpointType == AuthenticationProviderType.OnlineFederation) { - IdentityProvider provider = service.GetIdentityProvider(authCredentials.ClientCredentials.UserName.UserName); - if (provider != null && provider.IdentityProviderType == IdentityProviderType.LiveId) { - authCredentials.SupportingCredentials = new AuthenticationCredentials(); - authCredentials.SupportingCredentials.ClientCredentials = - Microsoft.Crm.Services.Utility.DeviceIdManager.LoadOrRegisterDevice(); - } - } - - break; - } - - return authCredentials; - } - } -} diff --git a/src/XrmMockup365/Database/XrmDb.cs b/src/XrmMockup365/Database/XrmDb.cs index 30055182..546d18fa 100644 --- a/src/XrmMockup365/Database/XrmDb.cs +++ b/src/XrmMockup365/Database/XrmDb.cs @@ -1,28 +1,28 @@ -using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using Microsoft.Xrm.Sdk.Metadata; -using Microsoft.Xrm.Sdk.Client; using Microsoft.Xrm.Sdk.Query; using System.Threading; using DG.Tools.XrmMockup.Serialization; using DG.Tools.XrmMockup.Internal; +using DG.Tools.XrmMockup.Online; namespace DG.Tools.XrmMockup.Database { internal class XrmDb { // Using ConcurrentDictionary for thread-safe table access in parallel test scenarios private ConcurrentDictionary TableDict = new ConcurrentDictionary(); - private Dictionary EntityMetadata; - private OrganizationServiceProxy OnlineProxy; + private readonly Dictionary EntityMetadata; + private readonly IOnlineDataService OnlineDataService; private int sequence; - public XrmDb(Dictionary entityMetadata, OrganizationServiceProxy onlineProxy) { + public XrmDb(Dictionary entityMetadata, IOnlineDataService onlineDataService) { this.EntityMetadata = entityMetadata; - this.OnlineProxy = onlineProxy; + this.OnlineDataService = onlineDataService; sequence = 0; } @@ -37,7 +37,7 @@ public DbTable this[string tableName] { } } - public void Add(Entity xrmEntity, bool withReferenceChecks = true) + public void Add(Entity xrmEntity, bool withReferenceChecks = true) { int nextSequence = Interlocked.Increment(ref sequence); var dbEntity = ToDbRow(xrmEntity,nextSequence, withReferenceChecks); @@ -113,9 +113,9 @@ internal void RegisterEntityMetadata(EntityMetadata entityMetadata) internal void PrefillDBWithOnlineData(QueryExpression queryExpr) { - if (OnlineProxy != null) + if (OnlineDataService != null) { - var onlineEntities = OnlineProxy.RetrieveMultiple(queryExpr).Entities; + var onlineEntities = OnlineDataService.RetrieveMultiple(queryExpr).Entities; foreach (var onlineEntity in onlineEntities) { if (this[onlineEntity.LogicalName][onlineEntity.Id] == null) @@ -133,13 +133,15 @@ internal DbRow GetDbRow(EntityReference reference, bool withReferenceCheck = tru if (reference?.Id != Guid.Empty) { currentDbRow = this[reference.LogicalName][reference.Id]; - if (currentDbRow == null && OnlineProxy != null) + if (currentDbRow == null && OnlineDataService != null) { if (!withReferenceCheck) + { currentDbRow = DbRow.MakeDBRowRef(reference, this); + } else { - var onlineEntity = OnlineProxy.Retrieve(reference.LogicalName, reference.Id, new ColumnSet(true)); + var onlineEntity = OnlineDataService.Retrieve(reference.LogicalName, reference.Id, new ColumnSet(true)); Add(onlineEntity, withReferenceCheck); currentDbRow = this[reference.LogicalName][reference.Id]; } @@ -165,7 +167,7 @@ internal DbRow GetDbRow(EntityReference reference, bool withReferenceCheck = tru // No identification given for the entity, throw error else { - throw new FaultException($"Missing a form of identification for the desired record in order to retrieve it."); + throw new FaultException("Missing a form of identification for the desired record in order to retrieve it."); } return currentDbRow; @@ -234,8 +236,7 @@ internal bool TryGetDbRow(EntityReference reference, out DbRow dbRow) internal DbRow GetDbRowOrNull(EntityReference reference) { - DbRow row; - if (TryGetDbRow(reference, out row)) + if (TryGetDbRow(reference, out DbRow row)) { return row; } @@ -247,8 +248,7 @@ internal DbRow GetDbRowOrNull(EntityReference reference) internal Entity GetEntityOrNull(EntityReference reference) { - DbRow row; - if (TryGetDbRow(reference, out row)) + if (TryGetDbRow(reference, out DbRow row)) { return row.ToEntity(); } @@ -263,7 +263,7 @@ internal Entity GetEntityOrNull(EntityReference reference) public XrmDb Clone() { var clonedTables = this.TableDict.ToDictionary(x => x.Key, x => x.Value.Clone()); - var clonedDB = new XrmDb(this.EntityMetadata, this.OnlineProxy) + var clonedDB = new XrmDb(this.EntityMetadata, this.OnlineDataService) { TableDict = new ConcurrentDictionary(clonedTables) }; @@ -281,7 +281,7 @@ public DbDTO ToSerializableDTO() public static XrmDb RestoreSerializableDTO(XrmDb current, DbDTO model) { var clonedTables = model.Tables.ToDictionary(x => x.Key, x => DbTable.RestoreSerializableDTO(new DbTable(current.EntityMetadata[x.Key]), x.Value)); - var clonedDB = new XrmDb(current.EntityMetadata, current.OnlineProxy) + var clonedDB = new XrmDb(current.EntityMetadata, current.OnlineDataService) { TableDict = new ConcurrentDictionary(clonedTables) }; diff --git a/src/XrmMockup365/FormulaFieldEvaluator.cs b/src/XrmMockup365/FormulaFieldEvaluator.cs index a6781a8d..11fb08dd 100644 --- a/src/XrmMockup365/FormulaFieldEvaluator.cs +++ b/src/XrmMockup365/FormulaFieldEvaluator.cs @@ -7,13 +7,14 @@ using System.Globalization; using System.Threading; using System.Threading.Tasks; +using PowerFxDataverseConnection = Microsoft.PowerFx.Dataverse.DataverseConnection; namespace DG.Tools.XrmMockup { internal class FormulaFieldEvaluator { private readonly IOrganizationService _organizationService; - private readonly DataverseConnection _dataverseConnection; + private readonly PowerFxDataverseConnection _dataverseConnection; public FormulaFieldEvaluator(IOrganizationServiceFactory serviceFactory) { diff --git a/src/XrmMockup365/Internal/CoreInitializationData.cs b/src/XrmMockup365/Internal/CoreInitializationData.cs index 4be6b99e..df78bbb9 100644 --- a/src/XrmMockup365/Internal/CoreInitializationData.cs +++ b/src/XrmMockup365/Internal/CoreInitializationData.cs @@ -1,7 +1,7 @@ -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Client; +using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; +using DG.Tools.XrmMockup.Online; namespace DG.Tools.XrmMockup.Internal { @@ -16,7 +16,7 @@ internal class CoreInitializationData public List SecurityRoles { get; set; } public EntityReference BaseCurrency { get; set; } public int BaseCurrencyPrecision { get; set; } - public OrganizationServiceProxy OnlineProxy { get; set; } + public IOnlineDataService OnlineDataService { get; set; } public Dictionary EntityTypeMap { get; set; } } } diff --git a/src/XrmMockup365/Online/IOnlineDataService.cs b/src/XrmMockup365/Online/IOnlineDataService.cs new file mode 100644 index 00000000..21c31a11 --- /dev/null +++ b/src/XrmMockup365/Online/IOnlineDataService.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; + +namespace DG.Tools.XrmMockup.Online +{ + /// + /// Interface for fetching data from an online Dataverse environment. + /// + internal interface IOnlineDataService : IDisposable + { + /// + /// Retrieves a single entity by ID. + /// + Entity Retrieve(string entityName, Guid id, ColumnSet columnSet); + + /// + /// Retrieves multiple entities using a QueryExpression. + /// + EntityCollection RetrieveMultiple(QueryExpression query); + + /// + /// Gets whether the service is connected. + /// + bool IsConnected { get; } + } +} diff --git a/src/XrmMockup365/Online/OnlineDataService.cs b/src/XrmMockup365/Online/OnlineDataService.cs new file mode 100644 index 00000000..e25b1927 --- /dev/null +++ b/src/XrmMockup365/Online/OnlineDataService.cs @@ -0,0 +1,63 @@ +#if DATAVERSE_SERVICE_CLIENT +using System; +using DataverseConnection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; + +namespace DG.Tools.XrmMockup.Online +{ + /// + /// In-process implementation for connecting to a live Dataverse environment. + /// Used for testing with real data when OnlineEnvironment is configured. + /// Uses Azure DefaultAzureCredential for authentication (supports managed identity, + /// Visual Studio credentials, Azure CLI, etc.). + /// + internal class OnlineDataService : IOnlineDataService + { + private readonly ServiceProvider _serviceProvider; + private readonly ServiceClient _serviceClient; + private bool _disposed; + + public OnlineDataService(string environmentUrl) + { + if (string.IsNullOrWhiteSpace(environmentUrl)) + throw new ArgumentNullException(nameof(environmentUrl)); + + // Use DataverseConnection for authentication + var services = new ServiceCollection(); + services.AddDataverse(options => options.DataverseUrl = environmentUrl); + _serviceProvider = services.BuildServiceProvider(); + _serviceClient = _serviceProvider.GetRequiredService(); + } + + public bool IsConnected => _serviceClient?.IsReady == true; + + public Entity Retrieve(string entityName, Guid id, ColumnSet columnSet) + { + if (entityName == null) throw new ArgumentNullException(nameof(entityName)); + if (columnSet == null) throw new ArgumentNullException(nameof(columnSet)); + + return _serviceClient.Retrieve(entityName, id, columnSet); + } + + public EntityCollection RetrieveMultiple(QueryExpression query) + { + if (query == null) throw new ArgumentNullException(nameof(query)); + + return _serviceClient.RetrieveMultiple(query); + } + + public void Dispose() + { + if (_disposed) + return; + + _disposed = true; + _serviceClient?.Dispose(); + _serviceProvider?.Dispose(); + } + } +} +#endif diff --git a/src/XrmMockup365/StaticMetadataCache.cs b/src/XrmMockup365/StaticMetadataCache.cs index cb630bd2..7a081a6c 100644 --- a/src/XrmMockup365/StaticMetadataCache.cs +++ b/src/XrmMockup365/StaticMetadataCache.cs @@ -1,7 +1,7 @@ -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Client; +using Microsoft.Xrm.Sdk; using System; using System.Collections.Generic; +using DG.Tools.XrmMockup.Online; namespace DG.Tools.XrmMockup { @@ -13,11 +13,11 @@ public class StaticMetadataCache public Dictionary EntityTypeMap { get; } public EntityReference BaseCurrency { get; } public int BaseCurrencyPrecision { get; } - public OrganizationServiceProxy OnlineProxy { get; } + internal IOnlineDataService OnlineDataService { get; } - public StaticMetadataCache(MetadataSkeleton metadata, List workflows, List securityRoles, - Dictionary entityTypeMap, EntityReference baseCurrency, int baseCurrencyPrecision, - OrganizationServiceProxy onlineProxy) + internal StaticMetadataCache(MetadataSkeleton metadata, List workflows, List securityRoles, + Dictionary entityTypeMap, EntityReference baseCurrency, int baseCurrencyPrecision, + IOnlineDataService onlineDataService) { Metadata = metadata; Workflows = workflows; @@ -25,7 +25,7 @@ public StaticMetadataCache(MetadataSkeleton metadata, List workflows, Li EntityTypeMap = entityTypeMap; BaseCurrency = baseCurrency; BaseCurrencyPrecision = baseCurrencyPrecision; - OnlineProxy = onlineProxy; + OnlineDataService = onlineDataService; } } } diff --git a/src/XrmMockup365/XrmMockup.cs b/src/XrmMockup365/XrmMockup.cs index aefe32c4..f7970bb8 100644 --- a/src/XrmMockup365/XrmMockup.cs +++ b/src/XrmMockup365/XrmMockup.cs @@ -73,8 +73,8 @@ public static XrmMockup365 GetInstance(XrmMockup365 xrmMockup, XrmMockupSettings { // Create a new cache entry using the existing instance's data cache = new StaticMetadataCache( - xrmMockup.Metadata, - xrmMockup.Workflows, + xrmMockup.Metadata, + xrmMockup.Workflows, xrmMockup.SecurityRoles, new Dictionary(), // Will be rebuilt if needed xrmMockup.BaseCurrency, diff --git a/src/XrmMockup365/XrmMockup365.csproj b/src/XrmMockup365/XrmMockup365.csproj index a1cddfb6..dd774f77 100644 --- a/src/XrmMockup365/XrmMockup365.csproj +++ b/src/XrmMockup365/XrmMockup365.csproj @@ -5,7 +5,7 @@ portable XrmMockup365 - 0.0.0 + 1.18.0-rc.6 @@ -62,17 +62,23 @@ - + - + + + + + <_Parameter1>XrmMockup365Test + + - + \ No newline at end of file diff --git a/src/XrmMockup365/XrmMockupBase.cs b/src/XrmMockup365/XrmMockupBase.cs index 4551d936..4202d1d5 100644 --- a/src/XrmMockup365/XrmMockupBase.cs +++ b/src/XrmMockup365/XrmMockupBase.cs @@ -285,6 +285,15 @@ public void PopulateWith(params Entity[] entities) { Core.PopulateWith(entities); } + /// + /// Prefills the local database with data from the online service based on the query. + /// Only works when OnlineDataServiceFactory or OnlineEnvironment is configured. + /// + /// The query to execute against the online service. + public void PrefillDBWithOnlineData(QueryExpression query) { + Core.PrefillDBWithOnlineData(query); + } + /// /// Create a new user with a specific businessunit /// diff --git a/src/XrmMockup365/XrmMockupSettings.cs b/src/XrmMockup365/XrmMockupSettings.cs index 69f4d67e..78a61e6b 100644 --- a/src/XrmMockup365/XrmMockupSettings.cs +++ b/src/XrmMockup365/XrmMockupSettings.cs @@ -1,8 +1,10 @@ -using Microsoft.Xrm.Sdk.Client; using System; using System.Collections.Generic; using Microsoft.Xrm.Sdk.Organization; using System.Reflection; +#if DATAVERSE_SERVICE_CLIENT +using DG.Tools.XrmMockup.Online; +#endif namespace DG.Tools.XrmMockup { @@ -43,10 +45,14 @@ public class XrmMockupSettings /// public IEnumerable ExceptionFreeRequests { get; set; } +#if DATAVERSE_SERVICE_CLIENT /// - /// Environment settings for connection to an online environment for live debugging. + /// Settings for connecting to an online Dataverse environment for live debugging. + /// Uses Azure DefaultAzureCredential for authentication (supports managed identity, + /// Visual Studio credentials, Azure CLI, etc.). /// public Env? OnlineEnvironment { get; set; } +#endif /// /// Overwrites the path to the directory containing metadata files. Default is '../../Metadata/'. @@ -98,15 +104,27 @@ public class XrmMockupSettings /// Default is true. /// public bool EnablePowerFxFields { get; set; } = true; - } +#if DATAVERSE_SERVICE_CLIENT + /// + /// Optional factory for creating IOnlineDataService. For testing purposes. + /// If set, this takes precedence over OnlineEnvironment. + /// + internal Func OnlineDataServiceFactory { get; set; } +#endif + } +#if DATAVERSE_SERVICE_CLIENT + /// + /// Settings for connecting to an online Dataverse environment. + /// public struct Env { - public string uri; - public AuthenticationProviderType providerType; - public string username; - public string password; - public string domain; + /// + /// URL of the Dataverse environment (e.g., https://org.crm.dynamics.com). + /// Uses Azure DefaultAzureCredential for authentication. + /// + public string Url; } -} \ No newline at end of file +#endif +} diff --git a/tests/TestPluginAssembly365/TestPluginAssembly365.csproj b/tests/TestPluginAssembly365/TestPluginAssembly365.csproj index 8d8f57c1..f100b07d 100644 --- a/tests/TestPluginAssembly365/TestPluginAssembly365.csproj +++ b/tests/TestPluginAssembly365/TestPluginAssembly365.csproj @@ -27,7 +27,7 @@ - + diff --git a/tests/XrmMockup365Test/Online/MockOnlineDataService.cs b/tests/XrmMockup365Test/Online/MockOnlineDataService.cs new file mode 100644 index 00000000..29523ce9 --- /dev/null +++ b/tests/XrmMockup365Test/Online/MockOnlineDataService.cs @@ -0,0 +1,123 @@ +#if DATAVERSE_SERVICE_CLIENT +using System; +using System.Collections.Generic; +using System.Linq; +using DG.Tools.XrmMockup.Online; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; + +namespace DG.XrmMockupTest.Online +{ + /// + /// Mock implementation of IOnlineDataService for unit testing XrmMockup's online data integration. + /// Tracks all calls made to verify correct behavior. + /// + internal class MockOnlineDataService : IOnlineDataService + { + private readonly Dictionary<(string LogicalName, Guid Id), Entity> _entities = new Dictionary<(string, Guid), Entity>(); + + /// + /// List of all Retrieve calls made to this service. + /// + public List<(string EntityName, Guid Id, ColumnSet ColumnSet)> RetrieveCalls { get; } = new List<(string, Guid, ColumnSet)>(); + + /// + /// List of all RetrieveMultiple calls made to this service. + /// + public List RetrieveMultipleCalls { get; } = new List(); + + /// + /// Configures the mock to return the specified entity when retrieved. + /// + public void SetupEntity(Entity entity) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + _entities[(entity.LogicalName, entity.Id)] = entity; + } + + /// + /// Configures the mock to return multiple entities. + /// + public void SetupEntities(IEnumerable entities) + { + foreach (var entity in entities) + { + SetupEntity(entity); + } + } + + /// + /// Clears all configured entities. + /// + public void ClearEntities() + { + _entities.Clear(); + } + + /// + /// Clears all recorded calls. + /// + public void ClearCalls() + { + RetrieveCalls.Clear(); + RetrieveMultipleCalls.Clear(); + } + + public Entity Retrieve(string entityName, Guid id, ColumnSet columnSet) + { + RetrieveCalls.Add((entityName, id, columnSet)); + + if (_entities.TryGetValue((entityName, id), out var entity)) + { + return CloneEntity(entity, columnSet); + } + + throw new Exception($"Entity {entityName} with id {id} not found in mock online data service"); + } + + public EntityCollection RetrieveMultiple(QueryExpression query) + { + RetrieveMultipleCalls.Add(query); + + var matches = _entities.Values + .Where(e => e.LogicalName == query.EntityName) + .Select(e => CloneEntity(e, query.ColumnSet)) + .ToList(); + + return new EntityCollection(matches) { EntityName = query.EntityName }; + } + + public bool IsConnected => true; + + public void Dispose() + { + // Nothing to dispose + } + + private static Entity CloneEntity(Entity entity, ColumnSet columnSet) + { + var clone = new Entity(entity.LogicalName, entity.Id); + + if (columnSet.AllColumns) + { + foreach (var attr in entity.Attributes) + { + clone[attr.Key] = attr.Value; + } + } + else + { + foreach (var column in columnSet.Columns) + { + if (entity.Contains(column)) + { + clone[column] = entity[column]; + } + } + } + + return clone; + } + } +} +#endif diff --git a/tests/XrmMockup365Test/Online/OnlineDataServiceUnitTests.cs b/tests/XrmMockup365Test/Online/OnlineDataServiceUnitTests.cs new file mode 100644 index 00000000..fa783e0a --- /dev/null +++ b/tests/XrmMockup365Test/Online/OnlineDataServiceUnitTests.cs @@ -0,0 +1,233 @@ +#if DATAVERSE_SERVICE_CLIENT +using System; +using System.Linq; +using DG.Tools.XrmMockup; +using DG.XrmFramework.BusinessDomain.ServiceContext; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Xunit; + +namespace DG.XrmMockupTest.Online +{ + /// + /// Unit tests verifying XrmMockup's integration with IOnlineDataService. + /// Uses mock IOnlineDataService to verify correct behavior without real proxy. + /// Only available on net8.0 (DATAVERSE_SERVICE_CLIENT). + /// + public class OnlineDataServiceUnitTests : IClassFixture + { + private readonly XrmMockupFixture _fixture; + + public OnlineDataServiceUnitTests(XrmMockupFixture fixture) + { + _fixture = fixture; + } + + private (XrmMockup365 crm, MockOnlineDataService mockService) CreateMockupWithOnlineService() + { + var mockService = new MockOnlineDataService(); + var settings = new XrmMockupSettings + { + BasePluginTypes = _fixture.Settings.BasePluginTypes, + BaseCustomApiTypes = _fixture.Settings.BaseCustomApiTypes, + CodeActivityInstanceTypes = _fixture.Settings.CodeActivityInstanceTypes, + EnableProxyTypes = _fixture.Settings.EnableProxyTypes, + IncludeAllWorkflows = _fixture.Settings.IncludeAllWorkflows, + ExceptionFreeRequests = _fixture.Settings.ExceptionFreeRequests, + MetadataDirectoryPath = _fixture.Settings.MetadataDirectoryPath, + IPluginMetadata = _fixture.Settings.IPluginMetadata, + OnlineDataServiceFactory = () => mockService + }; + + var crm = XrmMockup365.GetInstance(settings); + return (crm, mockService); + } + + [Fact] + public void GetDbRow_EntityNotInDb_CallsOnlineServiceRetrieve() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + var service = crm.GetAdminService(); + + var onlineAccountId = Guid.NewGuid(); + var onlineAccount = new Entity(Account.EntityLogicalName, onlineAccountId) + { + ["name"] = "Online Account", + ["accountnumber"] = "ONLINE-001" + }; + mockService.SetupEntity(onlineAccount); + + // Act - Try to retrieve an account that doesn't exist locally + // This should trigger a call to the online service + var retrieved = service.Retrieve(Account.EntityLogicalName, onlineAccountId, new ColumnSet(true)); + + // Assert + Assert.Single(mockService.RetrieveCalls); + Assert.Equal(Account.EntityLogicalName, mockService.RetrieveCalls[0].EntityName); + Assert.Equal(onlineAccountId, mockService.RetrieveCalls[0].Id); + Assert.Equal("Online Account", retrieved.GetAttributeValue("name")); + } + + [Fact] + public void GetDbRow_EntityInDb_DoesNotCallOnlineService() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + var service = crm.GetAdminService(); + + // Create account locally + var localAccount = new Account { Name = "Local Account" }; + localAccount.Id = service.Create(localAccount); + + mockService.ClearCalls(); // Clear any calls from Create + + // Act - Retrieve the locally created account + var retrieved = service.Retrieve(Account.EntityLogicalName, localAccount.Id, new ColumnSet(true)); + + // Assert - No calls to online service since entity exists locally + Assert.Empty(mockService.RetrieveCalls); + Assert.Equal("Local Account", retrieved.GetAttributeValue("name")); + } + + [Fact] + public void GetDbRow_RetrievedEntityAddedToLocalDb() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + var service = crm.GetAdminService(); + + var onlineAccountId = Guid.NewGuid(); + var onlineAccount = new Entity(Account.EntityLogicalName, onlineAccountId) + { + ["name"] = "Online Account", + ["accountnumber"] = "ONLINE-002" + }; + mockService.SetupEntity(onlineAccount); + + // Act - First retrieve fetches from online + service.Retrieve(Account.EntityLogicalName, onlineAccountId, new ColumnSet(true)); + mockService.ClearCalls(); + + // Second retrieve should use local cache + var retrieved = service.Retrieve(Account.EntityLogicalName, onlineAccountId, new ColumnSet(true)); + + // Assert - Second retrieve should not call online service + Assert.Empty(mockService.RetrieveCalls); + Assert.Equal("Online Account", retrieved.GetAttributeValue("name")); + } + + [Fact] + public void PrefillDBWithOnlineData_CallsRetrieveMultiple() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + + var onlineAccount1 = new Entity(Account.EntityLogicalName, Guid.NewGuid()) + { + ["name"] = "Online Account 1" + }; + var onlineAccount2 = new Entity(Account.EntityLogicalName, Guid.NewGuid()) + { + ["name"] = "Online Account 2" + }; + mockService.SetupEntities(new[] { onlineAccount1, onlineAccount2 }); + + var query = new QueryExpression(Account.EntityLogicalName) + { + ColumnSet = new ColumnSet(true) + }; + + // Act + crm.PrefillDBWithOnlineData(query); + + // Assert + Assert.Single(mockService.RetrieveMultipleCalls); + Assert.Equal(Account.EntityLogicalName, mockService.RetrieveMultipleCalls[0].EntityName); + } + + [Fact] + public void PrefillDBWithOnlineData_AddsEntitiesToLocalDb() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + var service = crm.GetAdminService(); + + var onlineAccount1 = new Entity(Account.EntityLogicalName, Guid.NewGuid()) + { + ["name"] = "Online Account 1" + }; + var onlineAccount2 = new Entity(Account.EntityLogicalName, Guid.NewGuid()) + { + ["name"] = "Online Account 2" + }; + mockService.SetupEntities(new[] { onlineAccount1, onlineAccount2 }); + + var query = new QueryExpression(Account.EntityLogicalName) + { + ColumnSet = new ColumnSet(true) + }; + + // Act + crm.PrefillDBWithOnlineData(query); + mockService.ClearCalls(); + + // Retrieve entities - should come from local DB, not online + var retrieved1 = service.Retrieve(Account.EntityLogicalName, onlineAccount1.Id, new ColumnSet(true)); + var retrieved2 = service.Retrieve(Account.EntityLogicalName, onlineAccount2.Id, new ColumnSet(true)); + + // Assert - No online calls since entities are now local + Assert.Empty(mockService.RetrieveCalls); + Assert.Equal("Online Account 1", retrieved1.GetAttributeValue("name")); + Assert.Equal("Online Account 2", retrieved2.GetAttributeValue("name")); + } + + [Fact] + public void PrefillDBWithOnlineData_SkipsExistingEntities() + { + // Arrange + var (crm, mockService) = CreateMockupWithOnlineService(); + var service = crm.GetAdminService(); + + // Create a local account first + var localAccount = new Account { Name = "Local Account" }; + localAccount.Id = service.Create(localAccount); + + // Setup online service to return an account with the same ID but different name + var onlineAccount = new Entity(Account.EntityLogicalName, localAccount.Id) + { + ["name"] = "Online Account (should not overwrite)" + }; + mockService.SetupEntity(onlineAccount); + + var query = new QueryExpression(Account.EntityLogicalName) + { + ColumnSet = new ColumnSet(true) + }; + + // Act + crm.PrefillDBWithOnlineData(query); + + // Retrieve the entity + var retrieved = service.Retrieve(Account.EntityLogicalName, localAccount.Id, new ColumnSet(true)); + + // Assert - Local entity should NOT be overwritten + Assert.Equal("Local Account", retrieved.GetAttributeValue("name")); + } + + [Fact] + public void NoOnlineService_EntityNotFound_ThrowsException() + { + // Arrange - Create mockup without online service + var crm = XrmMockup365.GetInstance(_fixture.Settings); + var service = crm.GetAdminService(); + + var nonExistentId = Guid.NewGuid(); + + // Act & Assert + Assert.Throws(() => + service.Retrieve(Account.EntityLogicalName, nonExistentId, new ColumnSet(true))); + } + } +} +#endif diff --git a/tests/XrmMockup365Test/TestSettings.cs b/tests/XrmMockup365Test/TestSettings.cs index 01e15fa5..e3f513e1 100644 --- a/tests/XrmMockup365Test/TestSettings.cs +++ b/tests/XrmMockup365Test/TestSettings.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Query; using DG.XrmFramework.BusinessDomain.ServiceContext; using Xunit; using Xunit.Sdk; @@ -32,34 +31,5 @@ public void TestNoExceptionRequest() orgAdminUIService.Execute(req); } } - - [Fact(Skip = "Using real data")] - public void TestRealDataRetrieve() - { - var acc = new Account(new Guid("9155CF31-BA6A-E611-80E0-C4346BAC0E68")) - { - Name = "babuasd" - }; - orgRealDataService.Update(acc); - var retrieved = orgRealDataService.Retrieve(Account.EntityLogicalName, acc.Id, new ColumnSet(true)).ToEntity(); - Assert.Equal(acc.Name, retrieved.Name); - Assert.Equal("12321123312", retrieved.AccountNumber); - } - - [Fact(Skip = "Using real data")] - public void TestRealDataRetrieveMultiple() - { - var query = new QueryExpression(Account.EntityLogicalName) - { - ColumnSet = new ColumnSet(true), - PageInfo = new PagingInfo() - { - Count = 1000, - PageNumber = 1 - } - }; - var res = orgRealDataService.RetrieveMultiple(query); - Assert.True(res.Entities.Count > 0); - } } } diff --git a/tests/XrmMockup365Test/UnitTestBase.cs b/tests/XrmMockup365Test/UnitTestBase.cs index 3f7aeaab..00ce43a7 100644 --- a/tests/XrmMockup365Test/UnitTestBase.cs +++ b/tests/XrmMockup365Test/UnitTestBase.cs @@ -18,7 +18,6 @@ public abstract class ServiceWrapper public IOrganizationServiceAsync2 orgAdminUIService { get; protected set; } public IOrganizationServiceAsync2 orgAdminService { get; protected set; } public IOrganizationServiceAsync2 orgGodService { get; protected set; } - public IOrganizationServiceAsync2 orgRealDataService { get; protected set; } public IOrganizationServiceAsync2 testUser1Service { get; protected set; } public IOrganizationServiceAsync2 testUser2Service { get; protected set; } @@ -28,7 +27,6 @@ public abstract class ServiceWrapper public IOrganizationService orgAdminUIService { get; protected set; } public IOrganizationService orgAdminService { get; protected set; } public IOrganizationService orgGodService { get; protected set; } - public IOrganizationService orgRealDataService { get; protected set; } public IOrganizationService testUser1Service { get; protected set; } public IOrganizationService testUser2Service { get; protected set; } @@ -97,8 +95,6 @@ public UnitTestBase(XrmMockupFixture fixture) orgAdminUIService = crm.GetAdminService(new MockupServiceSettings(true, false, MockupServiceSettings.Role.UI)); orgGodService = crm.GetAdminService(new MockupServiceSettings(false, true, MockupServiceSettings.Role.SDK)); orgAdminService = crm.GetAdminService(); - // Skip real data service - it causes online connection issues and isn't needed for most tests - orgRealDataService = null; //create an admin user to run our impersonating user plugins as var adminUser = new Entity("systemuser") { Id = Guid.Parse("3b961284-cd7a-4fa3-af7e-89802e88dd5c") }; diff --git a/tests/XrmMockup365Test/UnitTestBaseNoProxyTypes.cs b/tests/XrmMockup365Test/UnitTestBaseNoProxyTypes.cs index dcd58783..3a704e64 100644 --- a/tests/XrmMockup365Test/UnitTestBaseNoProxyTypes.cs +++ b/tests/XrmMockup365Test/UnitTestBaseNoProxyTypes.cs @@ -7,12 +7,9 @@ namespace DG.XrmMockupTest { public class UnitTestBaseNoProxyTypes : IClassFixture { - private static DateTime _startTime { get; set; } - protected IOrganizationService orgAdminUIService; protected IOrganizationService orgAdminService; protected IOrganizationService orgGodService; - protected IOrganizationService orgRealDataService; protected XrmMockup365 crm; @@ -23,8 +20,6 @@ public UnitTestBaseNoProxyTypes(XrmMockupFixtureNoProxyTypes fixture) orgAdminUIService = crm.GetAdminService(new MockupServiceSettings(true, false, MockupServiceSettings.Role.UI)); orgGodService = crm.GetAdminService(new MockupServiceSettings(false, true, MockupServiceSettings.Role.SDK)); orgAdminService = crm.GetAdminService(); - // Skip real data service - it causes online connection issues and isn't needed for most tests - orgRealDataService = null; } public void Dispose() @@ -33,4 +28,4 @@ public void Dispose() // The instance will be garbage collected automatically } } -} \ No newline at end of file +} diff --git a/tests/XrmMockup365Test/UnitTestBaseNoReset.cs b/tests/XrmMockup365Test/UnitTestBaseNoReset.cs index 4e223afd..d89d8094 100644 --- a/tests/XrmMockup365Test/UnitTestBaseNoReset.cs +++ b/tests/XrmMockup365Test/UnitTestBaseNoReset.cs @@ -9,12 +9,9 @@ namespace DG.XrmMockupTest { public class UnitTestBaseNoReset : IClassFixture { - private static DateTime _startTime { get; set; } - protected IOrganizationService orgAdminUIService; protected IOrganizationService orgAdminService; protected IOrganizationService orgGodService; - protected IOrganizationService orgRealDataService; protected XrmMockup365 crm; @@ -25,8 +22,6 @@ public UnitTestBaseNoReset(XrmMockupFixture fixture) orgAdminUIService = crm.GetAdminService(new MockupServiceSettings(true, false, MockupServiceSettings.Role.UI)); orgGodService = crm.GetAdminService(new MockupServiceSettings(false, true, MockupServiceSettings.Role.SDK)); orgAdminService = crm.GetAdminService(); - // Skip real data service - it causes online connection issues and isn't needed for most tests - orgRealDataService = null; //create an admin user to run our impersonating user plugins as @@ -41,4 +36,4 @@ public UnitTestBaseNoReset(XrmMockupFixture fixture) } } } -} \ No newline at end of file +} diff --git a/tests/XrmMockup365Test/XrmMockup365Test.csproj b/tests/XrmMockup365Test/XrmMockup365Test.csproj index 9d4903e9..6ec0e892 100644 --- a/tests/XrmMockup365Test/XrmMockup365Test.csproj +++ b/tests/XrmMockup365Test/XrmMockup365Test.csproj @@ -43,17 +43,19 @@ - + - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/XrmMockup365Test/XrmmockupFixture.cs b/tests/XrmMockup365Test/XrmmockupFixture.cs index 7b94cad4..322427e4 100644 --- a/tests/XrmMockup365Test/XrmmockupFixture.cs +++ b/tests/XrmMockup365Test/XrmmockupFixture.cs @@ -2,20 +2,17 @@ using DG.Tools.XrmMockup; using Microsoft.Xrm.Sdk; using XrmPluginCore; -using Microsoft.Xrm.Sdk.Client; using System; using TestPluginAssembly365.Plugins.LegacyDaxif; using TestPluginAssembly365.Plugins.ServiceBased; public class XrmMockupFixture : IDisposable { - // Shared settings instances to ensure metadata cache hits + // Shared settings instance to ensure metadata cache hits private static XrmMockupSettings _sharedSettings; - private static XrmMockupSettings _sharedRealDataSettings; private static readonly object _settingsLock = new object(); - + public XrmMockupSettings Settings => _sharedSettings; - public XrmMockupSettings RealDataSettings => _sharedRealDataSettings; public XrmMockupFixture() { @@ -34,31 +31,6 @@ public XrmMockupFixture() MetadataDirectoryPath = GetMetadataPath(), IPluginMetadata = metaPlugins }; - - try - { - _sharedRealDataSettings = new XrmMockupSettings - { - BasePluginTypes = _sharedSettings.BasePluginTypes, - CodeActivityInstanceTypes = _sharedSettings.CodeActivityInstanceTypes, - EnableProxyTypes = _sharedSettings.EnableProxyTypes, - IncludeAllWorkflows = _sharedSettings.IncludeAllWorkflows, - ExceptionFreeRequests = _sharedSettings.ExceptionFreeRequests, - MetadataDirectoryPath = GetMetadataPath(), - OnlineEnvironment = new Env - { - providerType = AuthenticationProviderType.OnlineFederation, - uri = "https://exampleURL/XRMServices/2011/Organization.svc", - username = "exampleUser", - password = "examplePass" - } - }; - } - catch - { - // ignore - set to null - _sharedRealDataSettings = null; - } } } } diff --git a/tests/XrmMockup365Test/XrmmockupFixtureNoProxyTypes.cs b/tests/XrmMockup365Test/XrmmockupFixtureNoProxyTypes.cs index bb8b88c5..1b76b4c4 100644 --- a/tests/XrmMockup365Test/XrmmockupFixtureNoProxyTypes.cs +++ b/tests/XrmMockup365Test/XrmmockupFixtureNoProxyTypes.cs @@ -1,18 +1,15 @@ using DG.Some.Namespace; using DG.Tools.XrmMockup; using XrmPluginCore; -using Microsoft.Xrm.Sdk.Client; using System; public class XrmMockupFixtureNoProxyTypes : IDisposable { // Shared settings instances to ensure metadata cache hits private static XrmMockupSettings _sharedSettings; - private static XrmMockupSettings _sharedRealDataSettings; private static readonly object _settingsLock = new object(); public XrmMockupSettings Settings => _sharedSettings; - public XrmMockupSettings RealDataSettings => _sharedRealDataSettings; public XrmMockupFixtureNoProxyTypes() { @@ -30,31 +27,6 @@ public XrmMockupFixtureNoProxyTypes() MetadataDirectoryPath = GetMetadataPath(), IPluginMetadata = metaPlugins }; - - try - { - _sharedRealDataSettings = new XrmMockupSettings - { - BasePluginTypes = _sharedSettings.BasePluginTypes, - CodeActivityInstanceTypes = _sharedSettings.CodeActivityInstanceTypes, - EnableProxyTypes = _sharedSettings.EnableProxyTypes, - IncludeAllWorkflows = _sharedSettings.IncludeAllWorkflows, - ExceptionFreeRequests = _sharedSettings.ExceptionFreeRequests, - MetadataDirectoryPath = GetMetadataPath(), - OnlineEnvironment = new Env - { - providerType = AuthenticationProviderType.OnlineFederation, - uri = "https://exampleURL/XRMServices/2011/Organization.svc", - username = "exampleUser", - password = "examplePass" - } - }; - } - catch - { - // ignore - set to null - _sharedRealDataSettings = null; - } } } }