diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/CrmServiceAdapter.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/CrmServiceAdapter.cs index f21e8a9..aa828c1 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/CrmServiceAdapter.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/CrmServiceAdapter.cs @@ -49,6 +49,36 @@ public ExecuteMultipleResponse ExecuteMultiple(IEnumerable return (ExecuteMultipleResponse)this.crmSvc.Execute(executeMultipleRequest); } + /// + public ExecuteMultipleResponse ExecuteMultiple(IEnumerable requests, string username, bool continueOnError = true, bool returnResponses = true) + { + if (requests is null) + { + throw new ArgumentNullException(nameof(requests)); + } + + if (username is null) + { + throw new ArgumentNullException(nameof(username)); + } + + this.logger.LogDebug($"Executing {requests.Count()} requests as {username}."); + + var previousCallerObjectId = this.CallerAADObjectId; + ExecuteMultipleResponse response = null; + try + { + this.CallerAADObjectId = this.RetrieveAzureAdObjectIdByDomainName(username); + response = this.ExecuteMultiple(requests, continueOnError, returnResponses); + } + finally + { + this.CallerAADObjectId = previousCallerObjectId; + } + + return response; + } + /// public IEnumerable RetrieveSolutionComponentObjectIds(string solutionName, int componentType) { diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/ICrmServiceAdapter.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/ICrmServiceAdapter.cs index d451d62..8cb513a 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/ICrmServiceAdapter.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Adapters/ICrmServiceAdapter.cs @@ -54,6 +54,16 @@ public interface ICrmServiceAdapter : IOrganizationService /// The . ExecuteMultipleResponse ExecuteMultiple(IEnumerable requests, bool continueOnError = true, bool returnResponses = true); + /// + /// Execute multiple requests. + /// + /// The requests. + /// The user to impersonate. + /// Whether to continue on error. + /// Whether to return responses. + /// The . + ExecuteMultipleResponse ExecuteMultiple(IEnumerable requests, string username, bool continueOnError = true, bool returnResponses = true); + /// /// Updates the state and status for an entity. /// diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Constants.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Constants.cs index bf8ed03..0139a16 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Constants.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Constants.cs @@ -105,6 +105,11 @@ public static class Fields /// The name of the process. /// public const string Name = "name"; + + /// + /// The name of the process. + /// + public const string StateCode = "statecode"; } } @@ -204,6 +209,11 @@ public static class Fields /// The name of the SDK message processing step. /// public const string Name = "name"; + + /// + /// The name of the SDK message processing step. + /// + public const string StateCode = "statecode"; } } diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs index 735b6ff..4cb1b6f 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/ProcessDeploymentService.cs @@ -48,7 +48,7 @@ public void SetStatesBySolution(IEnumerable solutions, IEnumerable processes, IEnumerable proces this.logger.LogInformation($"Activating processes as {user}."); } + var requests = new List(); foreach (var deployedProcess in processes) { var stateCode = new OptionSetValue(Constants.Workflow.StateCodeActive); @@ -114,27 +115,39 @@ private void SetStates(IEnumerable processes, IEnumerable proces statusCode.Value = Constants.Workflow.StatusCodeInactive; } + if (stateCode.Value == deployedProcess.GetAttributeValue(Constants.Workflow.Fields.StateCode).Value) + { + this.logger.LogInformation($"Process {deployedProcess[Constants.Workflow.Fields.Name]} already has desired state. Skipping."); + continue; + } + this.logger.LogInformation($"Setting process status for {deployedProcess[Constants.Workflow.Fields.Name]} with statecode {stateCode.Value} and statuscode {statusCode.Value}"); // SetStateRequest is supposedly deprecated but UpdateRequest doesn't work for deactivating active flows - var setStateRequest = new SetStateRequest { EntityMoniker = deployedProcess.ToEntityReference(), State = stateCode, Status = statusCode }; - - try - { - if (!string.IsNullOrEmpty(user)) + requests.Add( + new SetStateRequest { - this.logger.LogInformation($"Impersonating {user} to activate processes."); + EntityMoniker = deployedProcess.ToEntityReference(), + State = stateCode, + Status = statusCode, + }); + } - this.crmSvc.Execute(setStateRequest, user, fallbackToExistingUser: true); - } - else - { - this.crmSvc.Execute(setStateRequest); - } - } - catch (Exception ex) + if (!requests.Any()) + { + return; + } + + var executeMultipleRes = string.IsNullOrEmpty(user) ? + this.crmSvc.ExecuteMultiple(requests, true, true) : this.crmSvc.ExecuteMultiple(requests, user, true, true); + + if (executeMultipleRes.IsFaulted) + { + this.logger.LogError("Error(s) encountered when setting process states."); + foreach (var failedResponse in executeMultipleRes.Responses.Where(r => r.Fault != null)) { - this.logger.LogError(ex, $"Status for process {deployedProcess.Attributes[Constants.Workflow.Fields.Name]} could not be set. {ex.Message}"); + var failedRequest = (SetStateRequest)requests[failedResponse.RequestIndex]; + this.logger.LogError($"Failed to set state for process {failedRequest.EntityMoniker.Name} with the following error: {failedResponse.Fault.Message}."); } } } @@ -143,7 +156,7 @@ private EntityCollection RetrieveProcesses(IEnumerable names) { var query = new QueryExpression(Constants.Workflow.LogicalName) { - ColumnSet = new ColumnSet(false), + ColumnSet = new ColumnSet(Constants.Workflow.Fields.Name, Constants.Workflow.Fields.StateCode, Constants.Workflow.Fields.Type), }; query.Criteria.AddCondition(Constants.Workflow.Fields.Name, ConditionOperator.In, names.ToArray()); query.Criteria.AddCondition(Constants.Workflow.Fields.Type, ConditionOperator.Equal, Constants.Workflow.TypeDefinition); diff --git a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/SdkStepDeploymentService.cs b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/SdkStepDeploymentService.cs index 959247f..af7fb48 100644 --- a/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/SdkStepDeploymentService.cs +++ b/src/Capgemini.PowerApps.PackageDeployerTemplate/Services/SdkStepDeploymentService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Capgemini.PowerApps.PackageDeployerTemplate.Adapters; + using Microsoft.Crm.Sdk.Messages; using Microsoft.Extensions.Logging; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; @@ -46,7 +47,9 @@ public void SetStatesBySolution(IEnumerable solutions, IEnumerable componentsToActivate, IEnumerable sdkSteps, IEnumerable sdkStepsToDeactivate) + private void SetStates(IEnumerable sdkSteps, IEnumerable sdkStepsToDeactivate = null) { if (sdkSteps is null) { return; } + var requests = new List(); foreach (var deployedSdkStep in sdkSteps) { - var stateCode = Constants.SdkMessageProcessingStep.StateCodeActive; - var statusCode = Constants.SdkMessageProcessingStep.StatusCodeActive; + var stateCode = new OptionSetValue(Constants.SdkMessageProcessingStep.StateCodeActive); + var statusCode = new OptionSetValue(Constants.SdkMessageProcessingStep.StatusCodeActive); if (sdkStepsToDeactivate != null && sdkStepsToDeactivate.Contains(deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name])) { - stateCode = Constants.SdkMessageProcessingStep.StateCodeInactive; - statusCode = Constants.SdkMessageProcessingStep.StatusCodeInactive; + stateCode.Value = Constants.SdkMessageProcessingStep.StateCodeInactive; + statusCode.Value = Constants.SdkMessageProcessingStep.StatusCodeInactive; } - this.logger.LogInformation($"Setting SDK step status for {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} with statecode {stateCode} and statuscode {statusCode}"); - if (!this.crmSvc.UpdateStateAndStatusForEntity(Constants.SdkMessageProcessingStep.LogicalName, deployedSdkStep.Id, stateCode, statusCode)) + if (stateCode.Value == deployedSdkStep.GetAttributeValue(Constants.SdkMessageProcessingStep.Fields.StateCode).Value) { - this.logger.LogError($"Status for SDK step {deployedSdkStep.Attributes[Constants.SdkMessageProcessingStep.Fields.Name]} could not be set."); + this.logger.LogInformation($"SDK step {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} already has desired state. Skipping."); + continue; + } + + this.logger.LogInformation($"Setting SDK step status for {deployedSdkStep[Constants.SdkMessageProcessingStep.Fields.Name]} with statecode {stateCode.Value} and statuscode {statusCode.Value}"); + requests.Add( + new SetStateRequest + { + EntityMoniker = deployedSdkStep.ToEntityReference(), + State = stateCode, + Status = statusCode, + }); + } + + if (!requests.Any()) + { + return; + } + + var executeMultipleRes = this.crmSvc.ExecuteMultiple(requests, true, true); + + if (executeMultipleRes.IsFaulted) + { + this.logger.LogError("Error(s) encountered when setting SDK step states."); + foreach (var failedResponse in executeMultipleRes.Responses.Where(r => r.Fault != null)) + { + var failedRequest = (SetStateRequest)requests[failedResponse.RequestIndex]; + this.logger.LogError($"Failed to set state for SDK step {failedRequest.EntityMoniker.Name} with the following error: {failedResponse.Fault.Message}."); } } } @@ -118,7 +148,9 @@ private EntityCollection RetrieveSdkSteps(IEnumerable names) { var query = new QueryExpression(Constants.SdkMessageProcessingStep.LogicalName) { - ColumnSet = new ColumnSet(false), + ColumnSet = new ColumnSet( + Constants.SdkMessageProcessingStep.Fields.Name, + Constants.SdkMessageProcessingStep.Fields.StateCode), }; query.Criteria.AddCondition(Constants.SdkMessageProcessingStep.Fields.Name, ConditionOperator.In, names.ToArray()); diff --git a/templates/build-and-test-job.yml b/templates/build-and-test-job.yml index c3ef716..3cd0fe0 100644 --- a/templates/build-and-test-job.yml +++ b/templates/build-and-test-job.yml @@ -84,6 +84,8 @@ jobs: PACKAGEDEPLOYER_SETTINGS_CONNREF_PDT_SHAREDAPPROVALS_D7DCB: $(TestEnvironment.Connection.Approvals) PACKAGEDEPLOYER_SETTINGS_ENVVAR_PDT_TESTVARIABLE: Any string PACKAGEDEPLOYER_SETTINGS_CONNBASEURL_pdt_5Fexample-20api: https://anyurl.com + PACKAGEDEPLOYER_SETTINGS_LICENSEDUSERNAME: ${{ parameters.username }} + PACKAGEDEPLOYER_SETTINGS_LICENSEDPASSWORD: ${{ parameters.password }} inputs: codeCoverageEnabled: true platform: '$(buildPlatform)' diff --git a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs index eb8b145..3d35cfa 100644 --- a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs +++ b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/ProcessDeploymentServiceTests.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; using System.Linq; - using System.ServiceModel; + using System.Linq.Expressions; using Capgemini.PowerApps.PackageDeployerTemplate.Adapters; using Capgemini.PowerApps.PackageDeployerTemplate.Services; using FluentAssertions; using Microsoft.Crm.Sdk.Messages; using Microsoft.Extensions.Logging; using Microsoft.Xrm.Sdk; + using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using Moq; using Xunit; @@ -58,30 +59,30 @@ public void SetStatesBySolution_NullSolutionsArgument_LogsNoSolutions() [Fact] public void SetStatesBySolution_ProcessInComponentsToDeactivateList_DeactivatesProcess() { - var processToDeactivateName = "Process to deactivate"; - var solutionProcesses = new List - { - new Entity(Constants.Workflow.LogicalName) - { - Id = Guid.NewGuid(), - Attributes = - { - { Constants.Workflow.Fields.Name, processToDeactivateName }, - }, - }, - }; + var solutionProcesses = new List { GetProcess(Constants.Workflow.StateCodeActive) }; this.MockBySolutionProcesses(solutionProcesses); + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.Is>( + reqs => reqs.Cast().Any( + req => + req.EntityMoniker.LogicalName == Constants.Workflow.LogicalName && + req.EntityMoniker.Id == solutionProcesses.First().Id && + req.State.Value == Constants.Workflow.StateCodeInactive && + req.Status.Value == Constants.Workflow.StatusCodeInactive)), + It.IsAny(), + It.IsAny()), + true); + + this.processDeploymentSvc.SetStatesBySolution( + Solutions, + new List + { + solutionProcesses.First().GetAttributeValue("name"), + }); - this.processDeploymentSvc.SetStatesBySolution(Solutions, new List { processToDeactivateName }); - - this.crmServiceAdapterMock.Verify( - svc => svc.Execute( - It.Is(u => - u.EntityMoniker.LogicalName == Constants.Workflow.LogicalName && - u.EntityMoniker.Id == solutionProcesses[0].Id && - u.State.Value == Constants.Workflow.StateCodeInactive && - u.Status.Value == Constants.Workflow.StatusCodeInactive)), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] @@ -90,20 +91,21 @@ public void SetStatesBySolution_WithUserParameter_ExecutesAsUser() var userToImpersonate = "licenseduser@domaincom"; var solutionProcesses = new List { - new Entity(Constants.Workflow.LogicalName) - { - Id = Guid.NewGuid(), - Attributes = - { - { Constants.Workflow.Fields.Name, "A process" }, - }, - }, + GetProcess(Constants.Workflow.StateCodeInactive), }; this.MockBySolutionProcesses(solutionProcesses); - - this.processDeploymentSvc.SetStatesBySolution(Solutions, user: userToImpersonate); - - this.crmServiceAdapterMock.Verify(svc => svc.Execute(It.IsAny(), userToImpersonate, true)); + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.IsAny>(), + userToImpersonate, + It.IsAny(), + It.IsAny())); + + this.processDeploymentSvc.SetStatesBySolution( + Solutions, user: userToImpersonate); + + this.crmServiceAdapterMock.VerifyAll(); } [Fact] @@ -143,10 +145,7 @@ public void SetStates_NoProcessesFound_LogsNoProcessesFound() [Fact] public void SetStates_FoundProcessCountDiffersFromProvidedNamesCount_LogsMismatchWarning() { - var foundProcesses = new List - { - new Entity(Constants.Workflow.LogicalName) { Attributes = { { Constants.Workflow.Fields.Name, "Found process" } } }, - }; + var foundProcesses = new List { GetProcess(Constants.Workflow.StateCodeActive) }; this.MockSetStatesProcesses(foundProcesses); var processesToFind = new List { @@ -162,60 +161,59 @@ public void SetStates_FoundProcessCountDiffersFromProvidedNamesCount_LogsMismatc [Fact] public void SetStates_ProcessInComponentsToActivateFound_ActivatesProcess() { - var foundProcesses = new List - { - new Entity(Constants.Workflow.LogicalName) { Attributes = { { Constants.Workflow.Fields.Name, "Found process" } } }, - }; + var foundProcesses = new List { GetProcess(Constants.Workflow.StateCodeInactive) }; this.MockSetStatesProcesses(foundProcesses); + this.MockExecuteMultipleResponse(); this.processDeploymentSvc.SetStates(new List { foundProcesses.First().GetAttributeValue(Constants.Workflow.Fields.Name), }); - this.crmServiceAdapterMock.Verify( - svc => svc.Execute( - It.Is(u => - u.EntityMoniker.LogicalName == Constants.Workflow.LogicalName && - u.EntityMoniker.Id == foundProcesses.First().Id && - u.State.Value == Constants.Workflow.StateCodeActive && - u.Status.Value == Constants.Workflow.StatusCodeActive)), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] public void SetStates_ProcessInComponentsToDeactivateFound_DectivatesProcess() { - var foundProcesses = new List - { - new Entity(Constants.Workflow.LogicalName) { Attributes = { { Constants.Workflow.Fields.Name, "Found process" } } }, - }; + var foundProcesses = new List { GetProcess(Constants.Workflow.StateCodeActive) }; this.MockSetStatesProcesses(foundProcesses); + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.Is>( + reqs => reqs.Cast().Any( + req => + req.EntityMoniker.LogicalName == Constants.Workflow.LogicalName && + req.EntityMoniker.Id == foundProcesses.First().Id && + req.State.Value == Constants.Workflow.StateCodeInactive && + req.Status.Value == Constants.Workflow.StatusCodeInactive)), + It.IsAny(), + It.IsAny()), + true); this.processDeploymentSvc.SetStates(Enumerable.Empty(), new List { foundProcesses.First().GetAttributeValue(Constants.Workflow.Fields.Name), }); - this.crmServiceAdapterMock.Verify( - svc => svc.Execute( - It.Is(u => - u.EntityMoniker.LogicalName == Constants.Workflow.LogicalName && - u.EntityMoniker.Id == foundProcesses.First().Id && - u.State.Value == Constants.Workflow.StateCodeInactive && - u.Status.Value == Constants.Workflow.StatusCodeInactive)), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] public void SetStates_WithUserParameter_ExecutesAsUser() { - var foundProcesses = new List - { - new Entity(Constants.Workflow.LogicalName) { Attributes = { { Constants.Workflow.Fields.Name, "Found process" } } }, - }; + var foundProcesses = new List { GetProcess(Constants.Workflow.StateCodeInactive) }; this.MockSetStatesProcesses(foundProcesses); var userToImpersonate = "licenseduser@domaincom"; + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.IsAny>(), + userToImpersonate, + It.IsAny(), + It.IsAny()), + true); this.processDeploymentSvc.SetStates( new List @@ -225,21 +223,25 @@ public void SetStates_WithUserParameter_ExecutesAsUser() Enumerable.Empty(), userToImpersonate); - this.crmServiceAdapterMock.Verify(svc => svc.Execute(It.IsAny(), userToImpersonate, true)); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] public void SetStates_WithError_LogsError() { - var foundProcesses = new List + var foundProcesses = new List { GetProcess(Constants.Workflow.StateCodeInactive) }; + this.MockSetStatesProcesses(foundProcesses); + var fault = new OrganizationServiceFault { Message = "Some error." }; + var response = new ExecuteMultipleResponse { - new Entity(Constants.Workflow.LogicalName) { Attributes = { { Constants.Workflow.Fields.Name, "Found process" } } }, + Results = new ParameterCollection + { + { "Responses", new ExecuteMultipleResponseItemCollection() }, + { "IsFaulted", true }, + }, }; - this.MockSetStatesProcesses(foundProcesses); - var exception = new FaultException(); - this.crmServiceAdapterMock - .Setup(svc => svc.Execute(It.IsAny())) - .Throws(exception); + response.Responses.Add(new ExecuteMultipleResponseItem { Fault = fault }); + this.MockExecuteMultipleResponse(response); this.processDeploymentSvc.SetStates( new List @@ -248,7 +250,48 @@ public void SetStates_WithError_LogsError() }, Enumerable.Empty()); - this.loggerMock.VerifyLog(l => l.LogError(exception, It.IsAny())); + this.loggerMock.VerifyLog(l => l.LogError(It.Is(s => s.Contains(fault.Message)))); + } + + private static Entity GetProcess(int stateCode) + { + return new Entity(Constants.Workflow.LogicalName, Guid.NewGuid()) + { + Attributes = + { + { + Constants.Workflow.Fields.Name, "Process" + }, + { + Constants.Workflow.Fields.StateCode, new OptionSetValue(stateCode) + }, + }, + }; + } + + private void MockExecuteMultipleResponse(ExecuteMultipleResponse response = null, Expression> expression = null, bool verifiable = false) + { + if (expression == null) + { + expression = svc => svc.ExecuteMultiple( + It.IsAny>(), + It.IsAny(), + It.IsAny()); + } + + if (response == null) + { + response = new ExecuteMultipleResponse(); + } + + var returnResult = this.crmServiceAdapterMock + .Setup(expression) + .Returns(response); + + if (verifiable) + { + returnResult.Verifiable(); + } } private void MockSetStatesProcesses(IList processes) diff --git a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/SdkStepsDeploymentServiceTests.cs b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/SdkStepsDeploymentServiceTests.cs index 0637d3a..8f3a470 100644 --- a/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/SdkStepsDeploymentServiceTests.cs +++ b/tests/Capgemini.PowerApps.PackageDeployerTemplate.UnitTests/Services/SdkStepsDeploymentServiceTests.cs @@ -3,11 +3,14 @@ using System; using System.Collections.Generic; using System.Linq; + using System.Linq.Expressions; using Capgemini.PowerApps.PackageDeployerTemplate.Adapters; using Capgemini.PowerApps.PackageDeployerTemplate.Services; using FluentAssertions; + using Microsoft.Crm.Sdk.Messages; using Microsoft.Extensions.Logging; using Microsoft.Xrm.Sdk; + using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using Moq; using Xunit; @@ -19,14 +22,13 @@ public class SdkStepsDeploymentServiceTests private readonly Mock loggerMock; private readonly Mock crmServiceAdapterMock; - private readonly SdkStepDeploymentService sdkStepSvc; + private readonly SdkStepDeploymentService sdkStepDeploymentSvc; public SdkStepsDeploymentServiceTests() { this.loggerMock = new Mock(); this.crmServiceAdapterMock = new Mock(); - - this.sdkStepSvc = new SdkStepDeploymentService(this.loggerMock.Object, this.crmServiceAdapterMock.Object); + this.sdkStepDeploymentSvc = new SdkStepDeploymentService(this.loggerMock.Object, this.crmServiceAdapterMock.Object); } [Fact] @@ -48,7 +50,7 @@ public void Construct_NullCrmService_ThrowsArgumentNullException() [Fact] public void SetStatesBySolution_NullSolutionsArgument_LogsNoSolutions() { - this.sdkStepSvc.SetStatesBySolution(null); + this.sdkStepDeploymentSvc.SetStatesBySolution(null); this.loggerMock.VerifyLog(x => x.LogInformation("No solutions were provided to activate SDK steps for.")); } @@ -56,35 +58,36 @@ public void SetStatesBySolution_NullSolutionsArgument_LogsNoSolutions() [Fact] public void SetStatesBySolution_SdkStepInComponentsToDeactivateList_DeactivatesSdkStep() { - var sdkStepToDeactivateName = "SDK step to deactivate"; - var solutionSdkSteps = new List - { - new Entity(Constants.SdkMessageProcessingStep.LogicalName) - { - Id = Guid.NewGuid(), - Attributes = - { - { Constants.SdkMessageProcessingStep.Fields.Name, sdkStepToDeactivateName }, - }, - }, - }; + var solutionSdkSteps = new List { GetSdkStep(Constants.SdkMessageProcessingStep.StateCodeActive) }; this.MockBySolutionSdkSteps(solutionSdkSteps); + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.Is>( + reqs => reqs.Cast().Any( + req => + req.EntityMoniker.LogicalName == Constants.SdkMessageProcessingStep.LogicalName && + req.EntityMoniker.Id == solutionSdkSteps.First().Id && + req.State.Value == Constants.SdkMessageProcessingStep.StateCodeInactive && + req.Status.Value == Constants.SdkMessageProcessingStep.StatusCodeInactive)), + It.IsAny(), + It.IsAny()), + true); + + this.sdkStepDeploymentSvc.SetStatesBySolution( + Solutions, + new List + { + solutionSdkSteps.First().GetAttributeValue("name"), + }); - this.sdkStepSvc.SetStatesBySolution(Solutions, new List { sdkStepToDeactivateName }); - - this.crmServiceAdapterMock.Verify( - c => c.UpdateStateAndStatusForEntity( - Constants.SdkMessageProcessingStep.LogicalName, - solutionSdkSteps[0].Id, - Constants.SdkMessageProcessingStep.StateCodeInactive, - Constants.SdkMessageProcessingStep.StatusCodeInactive), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] public void SetStates_ComponentsToActivateNull_ThrowsArgumentNullException() { - Action setStatesWithoutComponentsToActivate = () => this.sdkStepSvc.SetStates(null); + Action setStatesWithoutComponentsToActivate = () => this.sdkStepDeploymentSvc.SetStates(null); setStatesWithoutComponentsToActivate.Should().Throw(); } @@ -92,7 +95,7 @@ public void SetStates_ComponentsToActivateNull_ThrowsArgumentNullException() [Fact] public void SetStates_ComponentsToDeactivateNull_DoesNotThrow() { - Action setStatesWithoutComponentsToDeactivate = () => this.sdkStepSvc.SetStates(Enumerable.Empty()); + Action setStatesWithoutComponentsToDeactivate = () => this.sdkStepDeploymentSvc.SetStates(Enumerable.Empty()); setStatesWithoutComponentsToDeactivate.Should().NotThrow(); } @@ -100,7 +103,7 @@ public void SetStates_ComponentsToDeactivateNull_DoesNotThrow() [Fact] public void SetStates_NoSdkStepsPassed_LogsNoSdkStepsProvided() { - this.sdkStepSvc.SetStates(Enumerable.Empty()); + this.sdkStepDeploymentSvc.SetStates(Enumerable.Empty()); this.loggerMock.VerifyLog(l => l.LogInformation("No SDK steps were provided.")); } @@ -110,7 +113,7 @@ public void SetStates_NoSdkStepsFound_LogsNoSdkStepsFound() { this.MockSetStatesSdkSteps(new List()); - this.sdkStepSvc.SetStates(new List { "Not found SDK step" }); + this.sdkStepDeploymentSvc.SetStates(new List { "Not found SDK step" }); this.loggerMock.VerifyLog(l => l.LogInformation("No SDK steps were found with the names provided.")); } @@ -118,10 +121,7 @@ public void SetStates_NoSdkStepsFound_LogsNoSdkStepsFound() [Fact] public void SetStates_FoundSdkStepCountDiffersFromProvidedNamesCount_LogsMismatchWarning() { - var foundSdkSteps = new List - { - new Entity(Constants.SdkMessageProcessingStep.LogicalName) { Attributes = { { Constants.SdkMessageProcessingStep.Fields.Name, "Found SDK step" } } }, - }; + var foundSdkSteps = new List { GetSdkStep(Constants.SdkMessageProcessingStep.StateCodeActive) }; this.MockSetStatesSdkSteps(foundSdkSteps); var sdkStepsToFind = new List { @@ -129,7 +129,7 @@ public void SetStates_FoundSdkStepCountDiffersFromProvidedNamesCount_LogsMismatc "Not found SDK step", }; - this.sdkStepSvc.SetStates(sdkStepsToFind); + this.sdkStepDeploymentSvc.SetStates(sdkStepsToFind); this.loggerMock.VerifyLog(l => l.LogWarning($"Found {foundSdkSteps.Count} deployed SDK steps but expected {sdkStepsToFind.Count}.")); } @@ -137,47 +137,111 @@ public void SetStates_FoundSdkStepCountDiffersFromProvidedNamesCount_LogsMismatc [Fact] public void SetStates_SdkStepInComponentsToActivateFound_ActivatesSdkStep() { - var foundSdkSteps = new List - { - new Entity(Constants.SdkMessageProcessingStep.LogicalName) { Attributes = { { Constants.SdkMessageProcessingStep.Fields.Name, "Found SDK step" } } }, - }; + var foundSdkSteps = new List { GetSdkStep(Constants.SdkMessageProcessingStep.StateCodeInactive) }; this.MockSetStatesSdkSteps(foundSdkSteps); + this.MockExecuteMultipleResponse(); - this.sdkStepSvc.SetStates(new List + this.sdkStepDeploymentSvc.SetStates(new List { foundSdkSteps.First().GetAttributeValue(Constants.SdkMessageProcessingStep.Fields.Name), }); - this.crmServiceAdapterMock.Verify( - svc => svc.UpdateStateAndStatusForEntity( - Constants.SdkMessageProcessingStep.LogicalName, - foundSdkSteps.First().Id, - Constants.SdkMessageProcessingStep.StateCodeActive, - Constants.SdkMessageProcessingStep.StatusCodeActive), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); } [Fact] public void SetStates_SdkStepInComponentsToDeactivateFound_DectivatesSdkStep() { - var foundSdkSteps = new List - { - new Entity(Constants.SdkMessageProcessingStep.LogicalName) { Attributes = { { Constants.SdkMessageProcessingStep.Fields.Name, "Found SDK step" } } }, - }; + var foundSdkSteps = new List { GetSdkStep(Constants.SdkMessageProcessingStep.StateCodeActive) }; this.MockSetStatesSdkSteps(foundSdkSteps); - - this.sdkStepSvc.SetStates(Enumerable.Empty(), new List + this.MockExecuteMultipleResponse( + null, + svc => svc.ExecuteMultiple( + It.Is>( + reqs => reqs.Cast().Any( + req => + req.EntityMoniker.LogicalName == Constants.SdkMessageProcessingStep.LogicalName && + req.EntityMoniker.Id == foundSdkSteps.First().Id && + req.State.Value == Constants.SdkMessageProcessingStep.StateCodeInactive && + req.Status.Value == Constants.SdkMessageProcessingStep.StatusCodeInactive)), + It.IsAny(), + It.IsAny()), + true); + + this.sdkStepDeploymentSvc.SetStates(Enumerable.Empty(), new List { foundSdkSteps.First().GetAttributeValue(Constants.SdkMessageProcessingStep.Fields.Name), }); - this.crmServiceAdapterMock.Verify( - svc => svc.UpdateStateAndStatusForEntity( - Constants.SdkMessageProcessingStep.LogicalName, - foundSdkSteps.First().Id, - Constants.SdkMessageProcessingStep.StateCodeInactive, - Constants.SdkMessageProcessingStep.StatusCodeInactive), - Times.Once()); + this.crmServiceAdapterMock.VerifyAll(); + } + + [Fact] + public void SetStates_WithError_LogsError() + { + var foundSdkSteps = new List { GetSdkStep(Constants.SdkMessageProcessingStep.StateCodeInactive) }; + this.MockSetStatesSdkSteps(foundSdkSteps); + var fault = new OrganizationServiceFault { Message = "Some error." }; + var response = new ExecuteMultipleResponse + { + Results = new ParameterCollection + { + { "Responses", new ExecuteMultipleResponseItemCollection() }, + { "IsFaulted", true }, + }, + }; + response.Responses.Add(new ExecuteMultipleResponseItem { Fault = fault }); + this.MockExecuteMultipleResponse(response); + + this.sdkStepDeploymentSvc.SetStates( + new List + { + foundSdkSteps.First().GetAttributeValue(Constants.SdkMessageProcessingStep.Fields.Name), + }, + Enumerable.Empty()); + + this.loggerMock.VerifyLog(l => l.LogError(It.Is(s => s.Contains(fault.Message)))); + } + + private static Entity GetSdkStep(int stateCode) + { + return new Entity(Constants.SdkMessageProcessingStep.LogicalName, Guid.NewGuid()) + { + Attributes = + { + { + Constants.SdkMessageProcessingStep.Fields.Name, "SdkStep" + }, + { + Constants.SdkMessageProcessingStep.Fields.StateCode, new OptionSetValue(stateCode) + }, + }, + }; + } + + private void MockExecuteMultipleResponse(ExecuteMultipleResponse response = null, Expression> expression = null, bool verifiable = false) + { + if (expression == null) + { + expression = svc => svc.ExecuteMultiple( + It.IsAny>(), + It.IsAny(), + It.IsAny()); + } + + if (response == null) + { + response = new ExecuteMultipleResponse(); + } + + var returnResult = this.crmServiceAdapterMock + .Setup(expression) + .Returns(response); + + if (verifiable) + { + returnResult.Verifiable(); + } } private void MockSetStatesSdkSteps(IList sdkSteps)