Skip to content
This repository has been archived by the owner on Jun 16, 2024. It is now read-only.

Implemented RetrieveMetadataChangesRequest #538

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Metadata.Query;
using Microsoft.Xrm.Sdk.Query;
using System.Reflection;

namespace FakeXrmEasy.FakeMessageExecutors
{
public class RetrieveMetadataChangesRequestExecutor : IFakeMessageExecutor
{

public bool CanExecute(OrganizationRequest request)
{
return request is RetrieveMetadataChangesRequest;
}
public OrganizationResponse Execute(OrganizationRequest request, XrmFakedContext ctx)
{
var req = request as RetrieveMetadataChangesRequest;

var response = new RetrieveMetadataChangesResponse()
{
Results = new ParameterCollection
{
["EntityMetadata"] = ApplyFilter(req.Query, ctx.EntityMetadata.Values)
}
};

return response;
}

public Type GetResponsibleRequestType()
{
return typeof(RetrieveMetadataChangesRequest);
}

private EntityMetadataCollection ApplyFilter(EntityQueryExpression qry, IEnumerable<EntityMetadata> metadata)
{
var results = new EntityMetadataCollection();
results.AddRange(metadata
.Where(e => IsMatch(e, qry.Criteria))
.Select(e => Project(e, qry, qry.Properties)));

return results;
}

private bool IsMatch(object obj, MetadataFilterExpression criteria)
{
if (criteria == null)
return true;

if ((criteria.Conditions == null || criteria.Conditions.Count == 0) && (criteria.Filters == null || criteria.Filters.Count == 0))
return true;

if (criteria.Conditions != null)
{
foreach (var condition in criteria.Conditions)
{
var conditionMatch = IsMatch(obj, condition);

if (criteria.FilterOperator == LogicalOperator.And && !conditionMatch)
return false;
else if (criteria.FilterOperator == LogicalOperator.Or && conditionMatch)
return true;
}
}

if (criteria.Filters != null)
{
foreach (var filter in criteria.Filters)
{
var filterMatch = IsMatch(obj, filter);

if (criteria.FilterOperator == LogicalOperator.And && !filterMatch)
return false;
else if (criteria.FilterOperator == LogicalOperator.Or && filterMatch)
return true;
}
}

if (criteria.FilterOperator == LogicalOperator.And)
return true;

return false;
}

private bool IsMatch(object obj, MetadataConditionExpression condition)
{
var prop = obj.GetType().GetProperty(condition.PropertyName);

if (prop == null)
throw new Exception($"Unknown property {condition.PropertyName}");

var value = prop.GetValue(obj, null);

switch (condition.ConditionOperator)
{
case MetadataConditionOperator.Equals:
return value == condition.Value || (value != null && condition.Value != null && value.Equals(condition.Value));

case MetadataConditionOperator.GreaterThan:
return value != null && condition.Value != null && ((IComparable)value).CompareTo(condition.Value) > 0;

case MetadataConditionOperator.In:
foreach (var v in (Array) condition.Value)
{
if (value == v || (value != null && v != null && value.Equals(v)))
return true;
}
return false;

case MetadataConditionOperator.LessThan:
return value != null && condition.Value != null && ((IComparable)value).CompareTo(condition.Value) < 0;

case MetadataConditionOperator.NotEquals:
return (value == null ^ condition.Value == null) || (value != null && condition.Value != null && !value.Equals(condition.Value));

case MetadataConditionOperator.NotIn:
foreach (var v in (Array)condition.Value)
{
if ((value == null ^ v == null) || (value != null && v != null && value.Equals(v)))
return false;
}
return true;

default:
throw new Exception($"Unknown condition operator {condition.ConditionOperator}");
}
}

private T Project<T>(T obj, EntityQueryExpression qry, MetadataPropertiesExpression properties)
{
var props = obj.GetType().GetProperties();

if (properties != null && !properties.AllProperties)
{
props = props
.Where(p => properties.PropertyNames.Contains(p.Name))
.ToArray();
}

var result = Activator.CreateInstance(obj.GetType());

foreach (var prop in props)
{
var value = prop.GetValue(obj, null);

if (prop.PropertyType == typeof(AttributeMetadata[]) && qry.AttributeQuery != null)
{
var attrs = (AttributeMetadata[])value;

value = attrs
.Where(a => IsMatch(a, qry.AttributeQuery.Criteria))
.Select(a => Project(a, qry, qry.AttributeQuery.Properties))
.ToArray();
}
else if (prop.PropertyType == typeof(OneToManyRelationshipMetadata[]) && qry.RelationshipQuery != null)
{
var rels = (OneToManyRelationshipMetadata[])value;

value = rels
.Where(r => IsMatch(r, qry.RelationshipQuery.Criteria))
.Select(r => Project(r, qry, qry.RelationshipQuery.Properties))
.ToArray();
}
else if (prop.PropertyType == typeof(ManyToManyRelationshipMetadata[]) && qry.RelationshipQuery != null)
{
var rels = (ManyToManyRelationshipMetadata[])value;

value = rels
.Where(r => IsMatch(r, qry.RelationshipQuery.Criteria))
.Select(r => Project(r, qry, qry.RelationshipQuery.Properties))
.ToArray();
}
else if (prop.PropertyType == typeof(Label) && qry.LabelQuery != null)
{
var label = (Label)value;

var locLabels = label.LocalizedLabels.ToArray();
label.LocalizedLabels.Clear();
label.LocalizedLabels.AddRange(locLabels.Where(l => qry.LabelQuery.FilterLanguages.Contains(l.LanguageCode)));
}

prop.SetValue(result, value, null);
}

return (T) result;
}
}
}
1 change: 1 addition & 0 deletions FakeXrmEasy.Shared/FakeXrmEasy.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\XmlExtensionsForFetchXml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\AddListMembersListRequestExecutor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\FetchXmlToQueryExpressionRequestExecutor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\RetrieveMetadataChangesRequestExecutor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\RetrieveExchangeRateRequest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\BulkDeleteRequestExecutor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeMessageExecutors\RemoveMembersTeamRequestExecutor.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using Crm;
using FakeXrmEasy.FakeMessageExecutors;
using FakeXrmEasy.Extensions;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using System;
using System.Linq;
using System.Reflection;
using Xunit;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk.Metadata.Query;

namespace FakeXrmEasy.Tests.FakeContextTests.RetrieveMetadataChangesRequestTests
{
public class RetrieveMetadataChangesRequestTests
{
private IOrganizationService Arrange()
{
var ctx = new XrmFakedContext();
var service = ctx.GetOrganizationService();

var accountMetadata = new EntityMetadata()
{
LogicalName = Account.EntityLogicalName,
IsCustomizable = new BooleanManagedProperty(true)
};
var stringMetadata = new StringAttributeMetadata()
{
LogicalName = "name",
MaxLength = 200,
};
stringMetadata.SetSealedPropertyValue("IsValidForCreate", new Nullable<bool>(true));
accountMetadata.SetAttributeCollection(new List<AttributeMetadata>() { stringMetadata });

var contactMetadata = new EntityMetadata()
{
LogicalName = Contact.EntityLogicalName
};
var intMetadata = new IntegerAttributeMetadata()
{
LogicalName = "numberofchildren",
MinValue = 0,
MaxValue = 100
};
contactMetadata.SetAttributeCollection(new List<AttributeMetadata>() { intMetadata });
ctx.InitializeMetadata(new[] { accountMetadata, contactMetadata });

return service;
}

[Fact]
public void When_calling_RetrieveMetadataChanges_with_a_filter_only_the_requested_entity_is_returned()
{
var service = Arrange();

var request = new RetrieveMetadataChangesRequest()
{
Query = new EntityQueryExpression
{
Criteria = new MetadataFilterExpression
{
Conditions =
{
new MetadataConditionExpression(nameof(EntityMetadata.LogicalName), MetadataConditionOperator.Equals, Account.EntityLogicalName)
}
}
}
};

var response = service.Execute(request);
var metadata = Assert.IsType<RetrieveMetadataChangesResponse>(response);

var names = metadata.EntityMetadata.Select(e => e.LogicalName).ToArray();
Assert.Equal(new[] { Account.EntityLogicalName }, names);
}

[Fact]
public void When_calling_RetrieveMetadataChanges_with_an_attribute_filter_only_the_requested_attributes_are_returned()
{
var service = Arrange();

var request = new RetrieveMetadataChangesRequest()
{
Query = new EntityQueryExpression
{
AttributeQuery = new AttributeQueryExpression
{
Criteria = new MetadataFilterExpression
{
Conditions =
{
new MetadataConditionExpression(nameof(AttributeMetadata.LogicalName), MetadataConditionOperator.Equals, "name")
}
},
Properties = new MetadataPropertiesExpression(nameof(StringAttributeMetadata.MaxLength))
}
}
};

var response = service.Execute(request);
var metadata = Assert.IsType<RetrieveMetadataChangesResponse>(response);

var attributes = metadata.EntityMetadata.SelectMany(e => e.Attributes).ToArray();
Assert.Equal(1, attributes.Length);
var stringAttr = Assert.IsType<StringAttributeMetadata>(attributes.Single());
Assert.Equal(200, stringAttr.MaxLength);
}

[Fact]
public void When_calling_RetrieveMetadataChanges_and_get_attributes_of_different_types_properties_that_only_exist_in_one_type_are_returned()
{
var service = Arrange();

var request = new RetrieveMetadataChangesRequest()
{
Query = new EntityQueryExpression
{
AttributeQuery = new AttributeQueryExpression
{
Properties = new MetadataPropertiesExpression(nameof(StringAttributeMetadata.MaxLength), nameof(IntegerAttributeMetadata.MaxValue))
}
}
};

var response = service.Execute(request);
var metadata = Assert.IsType<RetrieveMetadataChangesResponse>(response);

var attributes = metadata.EntityMetadata.SelectMany(e => e.Attributes).ToArray();
Assert.Equal(2, attributes.Length);
var stringAttr = attributes.OfType<StringAttributeMetadata>().Single();
Assert.Equal(200, stringAttr.MaxLength);
var intAttr = attributes.OfType<IntegerAttributeMetadata>().Single();
Assert.Equal(100, intAttr.MaxValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RemoveMembersTeamRequestTests\Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveAttributeRequestTests\RetrieveAttributeRequestTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveEntityRequestTests\RetrieveEntityRequestTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveMetadataChangesRequestTests\RetrieveMetadataChangesRequestTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveMultiple\QueryLinkEntityTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveMultiple\RetrieveMultipleTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FakeContextTests\RetrieveOptionSetRequestTests\RetrieveOptionSetRequestTests.cs" />
Expand Down