Skip to content

Commit

Permalink
Merge branch 'master' into feature/add_file_upload_requests
Browse files Browse the repository at this point in the history
  • Loading branch information
mkholt authored Sep 10, 2024
2 parents 02adc2c + 231deb7 commit bf0f2e7
Show file tree
Hide file tree
Showing 16 changed files with 709 additions and 16 deletions.
5 changes: 5 additions & 0 deletions src/XrmMockupShared/Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ private void InitializeDB()
new CloseIncidentRequestHandler(this, db, metadata, security),
new AddMembersTeamRequestHandler(this, db, metadata, security),
new RemoveMembersTeamRequestHandler(this, db, metadata, security),
new SendEmailRequestHandler(this, db, metadata, security),
#if !(XRM_MOCKUP_2011 || XRM_MOCKUP_2013)
new IsValidStateTransitionRequestHandler(this, db, metadata, security),
new CalculateRollupFieldRequestHandler(this, db, metadata, security),
Expand Down Expand Up @@ -679,6 +680,10 @@ internal OrganizationResponse Execute(OrganizationRequest request, EntityReferen
pluginContext.OutputParameters["BusinessEntityCollection"] =
(response as RetrieveMultipleResponse)?.EntityCollection;
}
else if (request is RetrieveRequest)
{
pluginContext.OutputParameters["BusinessEntity"] = TryRetrieve((request as RetrieveRequest).Target);
}

if (!string.IsNullOrEmpty(eventOp))
{
Expand Down
8 changes: 4 additions & 4 deletions src/XrmMockupShared/Database/DbAttributeTypeMap.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Collections.Generic;
using System.Text;

namespace DG.Tools.XrmMockup.Database
{
Expand All @@ -27,7 +27,7 @@ internal static class DbAttributeTypeHelper
{ AttributeTypeCode.Money, typeof(decimal) },
{ AttributeTypeCode.Owner, typeof(DbRow) },
{ AttributeTypeCode.Customer, typeof(DbRow) },
{ AttributeTypeCode.PartyList, typeof(DbRow[]) },
{ AttributeTypeCode.PartyList, typeof(EntityCollection) },
{ AttributeTypeCode.String, typeof(string) },
{ AttributeTypeCode.Uniqueidentifier, typeof(Guid) },
{ AttributeTypeCode.Virtual, typeof(DbRow[]) }
Expand All @@ -37,7 +37,7 @@ public static bool IsValidType(AttributeMetadata attrMetadata, object value) {
if (value == null) return true;
_metadataTypeMap.TryGetValue(attrMetadata.AttributeType.Value, out Type expectedType);
if (expectedType == null) {
throw new NotImplementedException($"Attribute of type '{attrMetadata.AttributeType.Value}' is not implemeted in XrmMockup yet.");
throw new NotImplementedException($"Attribute of type '{attrMetadata.AttributeType.Value}' is not implemented in XrmMockup yet.");
}
if (expectedType.Name == "DbRow[]" && attrMetadata.AttributeType.Value == AttributeTypeCode.Virtual)
return true;
Expand Down
1 change: 1 addition & 0 deletions src/XrmMockupShared/Mappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal static partial class Mappings {
{ typeof(UpdateRequest), "Target" },
{ typeof(AssociateRequest), "Target" },
{ typeof(DisassociateRequest), "Target" },
{ typeof(RetrieveRequest), "Target" },
};

public static Dictionary<Type, string> RequestToEventOperation = new Dictionary<Type, string>()
Expand Down
23 changes: 12 additions & 11 deletions src/XrmMockupShared/Requests/CloseIncidentRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using System.Linq;
using Microsoft.Crm.Sdk.Messages;
using System.ServiceModel;
Expand Down Expand Up @@ -80,12 +76,6 @@ internal override OrganizationResponse Execute(OrganizationRequest orgRequest, E
throw new FaultException($"incident With Id = {incidentRef.Id} is allready cancelled and connot be closed");
}

var setStaterequest = new SetStateRequest();
setStaterequest.EntityMoniker = incidentRef;
setStaterequest.State = new OptionSetValue(1);
setStaterequest.Status = request.Status;
core.Execute(setStaterequest, userRef);

var incidentResolution = db.GetDbRowOrNull(request.IncidentResolution.ToEntityReference());

if (incidentResolution != null)
Expand All @@ -95,6 +85,17 @@ internal override OrganizationResponse Execute(OrganizationRequest orgRequest, E

db.Add(request.IncidentResolution);

incident["statecode"] = new OptionSetValue(1);
incident["statuscode"] = request.Status;

var updateRequest = new UpdateRequest
{
Target = incident,
["CloseIncidentRequestHandler"] = true,
};

core.Execute(updateRequest, userRef);

return new CloseIncidentResponse();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ internal override OrganizationResponse Execute(OrganizationRequest orgRequest, E
colToReturn = new EntityCollection(collection.Select(x => x.Value).ToList());
}

if (queryExpr.Distinct)
{
var uniqueIds = new HashSet<Guid>();
colToReturn = new EntityCollection(colToReturn.Entities.Where(entity => uniqueIds.Add(entity.Id)).ToList());
}

// According to docs, should return -1 if ReturnTotalRecordCount set to false
colToReturn.TotalRecordCount = queryExpr.PageInfo.ReturnTotalRecordCount ? colToReturn.Entities.Count : -1;

Expand Down
140 changes: 140 additions & 0 deletions src/XrmMockupShared/Requests/SendEmailRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using DG.Tools.XrmMockup.Database;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using System;
using System.ServiceModel;

namespace DG.Tools.XrmMockup
{
internal class SendEmailRequestHandler : RequestHandler
{
const int EMAIL_STATE_COMPLETED = 1;
const int EMAIL_STATUS_DRAFT = 1;
const int EMAIL_STATUS_PENDING_SEND = 6;
const int EMAIL_STATUS_SENT = 3;

public SendEmailRequestHandler(Core core, XrmDb db, MetadataSkeleton metadata, Security security) : base(core, db, metadata, security, "SendEmail") { }

internal override void CheckSecurity(OrganizationRequest orgRequest, EntityReference userRef)
{
var request = MakeRequest<SendEmailRequest>(orgRequest);
if (request.EmailId == Guid.Empty)
{
throw new FaultException("Required field 'EmailId' is missing");
}

var emailRef = new EntityReference("email", request.EmailId);

if (!security.HasPermission(emailRef, AccessRights.ReadAccess, userRef))
{
throw new FaultException($"Principal user (Id={userRef.Id}) is missing Read privilege for email (Id={emailRef.Id})");
}

if (!security.HasPermission(emailRef, AccessRights.WriteAccess, userRef))
{
throw new FaultException($"Principal user (Id={userRef.Id}) is missing Write privilege for email (Id={emailRef.Id})");
}

var email = db.GetEntityOrNull(emailRef) ?? throw new FaultException($"email with Id = {request.EmailId} does not exist");

if (email.Contains("regardingobjectid"))
{
var regardingObject = email.GetAttributeValue<EntityReference>("regardingobjectid");

if (!security.HasPermission(regardingObject, AccessRights.ReadAccess, userRef))
{
throw new FaultException($"Principal user (Id={userRef.Id}) is missing Read privilege for {regardingObject.LogicalName} (Id={regardingObject.Id})");
}
}

// Remaining security checks have been omitted to reduce complexity
}

internal override OrganizationResponse Execute(OrganizationRequest orgRequest, EntityReference userRef)
{
var request = MakeRequest<SendEmailRequest>(orgRequest);

var email = db.GetEntity(new EntityReference("email", request.EmailId));

if (email.GetAttributeValue<OptionSetValue>("statuscode").Value != EMAIL_STATUS_DRAFT)
{
throw new FaultException("Email must be in Draft status to send");
}

if (email.GetAttributeValue<bool>("directioncode") is false)
{
throw new FaultException("Cannot send incoming email messages");
}

if (email.Contains("from"))
{
var from = email.GetAttributeValue<EntityCollection>("from");
if (from.Entities.Count != 1)
{
throw new FaultException("The email must have one and only one sender");
}

var activityParty = from.Entities[0];
if (activityParty.Contains("partyid"))
{
var partyRef = activityParty.GetAttributeValue<EntityReference>("partyid");
if (db.GetEntityOrNull(partyRef) is null)
{
throw new FaultException($"{partyRef.LogicalName} with Id = {partyRef.Id} does not exist");
}
}
else
{
throw new FaultException("Sender cannot be unresolved");
}
}
else
{
throw new FaultException("The email must have a sender");
}

if (email.Contains("to"))
{
var to = email.GetAttributeValue<EntityCollection>("to");
if (to.Entities.Count is 0)
{
throw new FaultException("The email must have at least one recipient before it can be sent");
}

foreach (Entity activityParty in to.Entities)
{
if (activityParty.Contains("partyid"))
{
var partyRef = activityParty.GetAttributeValue<EntityReference>("partyid");
if (db.GetEntityOrNull(partyRef) is null)
{
throw new FaultException($"{partyRef.LogicalName} with Id = {partyRef.Id} does not exist");
}
}
else if (!activityParty.Contains("addressused"))
{
throw new FaultException("Invalid ActivityParty");
}
}
}
else
{
throw new FaultException("The email must have at least one recipient before it can be sent");
}


email["statecode"] = new OptionSetValue(EMAIL_STATE_COMPLETED);
email["statuscode"] = new OptionSetValue(request.IssueSend ? EMAIL_STATUS_PENDING_SEND : EMAIL_STATUS_SENT);

db.Update(email);

return new SendEmailResponse
{
Results = new ParameterCollection
{
{ "Subject", email.Contains("subject") ? email.GetAttributeValue<string>("subject") : null }
}
};
}
}
}
8 changes: 7 additions & 1 deletion src/XrmMockupShared/Requests/UpdateRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ internal override OrganizationResponse Execute(OrganizationRequest orgRequest, E
var request = MakeRequest<UpdateRequest>(orgRequest);
var settings = MockupExecutionContext.GetSettings(request);

if (request.Target.LogicalName is "incident" &&
request.Target.GetAttributeValue<OptionSetValue>("statecode").Value is 1 &&
!request.Parameters.ContainsKey("CloseIncidentRequestHandler"))
{
throw new FaultException("This message can not be used to set the state of incident to Resolved. In order to set state of incident to Resolved, use the CloseIncidentRequest message instead.");
}

var entRef = request.Target.ToEntityReferenceWithKeyAttributes();
var entity = request.Target;
var row = db.GetDbRow(entRef);

if (settings.ServiceRole == MockupServiceSettings.Role.UI &&
Expand Down
1 change: 1 addition & 0 deletions src/XrmMockupShared/XrmMockupShared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Requests\CommitFileBlocksUploadRequestHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Requests\UploadBlockRequestHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Requests\InitializeFileBlocksUploadRequestHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Requests\SendEmailRequestHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Serialization\DbDTO.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Serialization\OptionSetCollectionDTO.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Serialization\EntityReferenceDTO.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using DG.XrmFramework.BusinessDomain.ServiceContext;
using System.Linq;

namespace DG.Some.Namespace
{
public class IncidentDeleteAllRelatedResolutionsOnClose : Plugin
{
public IncidentDeleteAllRelatedResolutionsOnClose() : base(typeof(IncidentDeleteAllRelatedResolutionsOnClose))
{
RegisterPluginStep<Incident>(
EventOperation.Update,
ExecutionStage.PostOperation,
ExecuteDeleteAllRelatedResolutionsOnClose)
.AddFilteredAttributes(x => x.StateCode)
.AddImage(ImageType.PreImage, x => x.StateCode, x => x.Title)
.AddImage(ImageType.PostImage, x => x.StateCode)
.SetExecutionOrder(10);
}

protected void ExecuteDeleteAllRelatedResolutionsOnClose(LocalPluginContext localContext)
{
var preImage = localContext.PluginExecutionContext.PreEntityImages["PreImage"].ToEntity<Incident>();
if (preImage.Title != "TestRemovalOfResolutionsAfterClose") return;

var postImage = localContext.PluginExecutionContext.PostEntityImages["PostImage"].ToEntity<Incident>();

if (preImage.StateCode == IncidentState.Active && postImage.StateCode != IncidentState.Active)
{
using (var context = new Xrm(localContext.OrganizationService))
{
context.IncidentResolutionSet
.Where(x => x.IncidentId.Id == localContext.PluginExecutionContext.PrimaryEntityId)
.ToList()
.ForEach(x => localContext.OrganizationAdminService.Delete(x.LogicalName, x.Id));
}
}
}
}
}
27 changes: 27 additions & 0 deletions tests/SharedPluginsAndCodeactivites/RetrievePlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using DG.XrmFramework.BusinessDomain.ServiceContext;
using Microsoft.Xrm.Sdk;


namespace DG.Some.Namespace
{
public class RetrievePlugin : Plugin
{
public RetrievePlugin() : base(typeof(RetrievePlugin))
{
RegisterPluginStep<Contact>(
EventOperation.Retrieve,
ExecutionStage.PostOperation,
ExecutePostRetrieve);
}

protected void ExecutePostRetrieve(LocalPluginContext localContext)
{
var entity = localContext.PluginExecutionContext.OutputParameters["BusinessEntity"] as Entity;

if (entity.ToEntity<Contact>().StateCode == ContactState.Inactive)
{
throw new InvalidPluginExecutionException("Inactive contacts cannot be retrieved.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Closeincidentplugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IncidentDeleteAllRelatedResolutionsOnClose.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ParentPostCreatePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ChildPreCreatePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NotePostOperationPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BusTicketSync.cs" />
<Compile Include="$(MSBuildThisFileDirectory)AccountCustomActivity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)AccountBothImagePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FaxPreOperationPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RetrievePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UpdateIdInCreatePlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ContactPostPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectPlugins\ContactIPluginDirectPostOp.cs" />
Expand Down
1 change: 1 addition & 0 deletions tests/SharedTests/SharedTests.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<Compile Include="$(MSBuildThisFileDirectory)TestCWAAccountOptional.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestDefaultBusinessUnitTeamsMembers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestPriorityAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestSendEmail.cs" />
<Compile Include="..\SharedTests\TestZipSnapshot.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestTableReset.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TestPrivileges\TestPrivilegesAppendToOnUpdate.cs" />
Expand Down
Loading

0 comments on commit bf0f2e7

Please sign in to comment.