diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs new file mode 100644 index 000000000..0c7bd983e --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/EdmModelBuilder.cs @@ -0,0 +1,96 @@ +using Microsoft.FullyQualified.NS; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; + +namespace Microsoft.AspNetCore.OData.Tests.Query.Expressions +{ + internal static class EdmModelBuilder + { + private static readonly IEdmModel Model = BuildAndGetEdmModel(); + + public static IEdmModel TestModel + { + get { return Model; } + } + + public static IEdmEntityType GetEntityType(string entityQualifiedName) + { + return TestModel.FindDeclaredType(entityQualifiedName) as IEdmEntityType; + } + + public static IEdmComplexType GetEdmComplexType(string complexTypeQualifiedName) + { + return TestModel.FindDeclaredType(complexTypeQualifiedName) as IEdmComplexType; + } + + public static IEdmEntityTypeReference GetEntityTypeReference(IEdmEntityType entityType) + { + return new EdmEntityTypeReference(entityType, false); + } + + public static IEdmComplexTypeReference GetComplexTypeReference(IEdmComplexType complexType) + { + // Create a complex type reference using the EdmCoreModel + return new EdmComplexTypeReference(complexType, isNullable: false); + } + + public static IEdmProperty GetPersonLocationProperty() + { + return GetEntityType("Microsoft.FullyQualified.NS.Person").FindProperty("Location"); + } + + /// + /// Get the entity set from the model + /// + /// People Set + public static IEdmEntitySet GetPeopleSet() + { + return TestModel.EntityContainer.FindEntitySet("People"); + } + + public static IEdmModel BuildAndGetEdmModel() + { + var builder = new ODataConventionModelBuilder(); + builder.Namespace = "Microsoft.FullyQualified.NS"; + builder.EntitySet("People"); + builder.ComplexType(); + builder.ComplexType(); + builder.EntitySet("Employees"); + + return builder.GetEdmModel(); + } + } +} + +namespace Microsoft.FullyQualified.NS +{ + public class Person + { + public int Id { get; set; } + public string FullName { get; set; } + public MyAddress Location { get; set; } + } + + public class Employee : Person + { + public string EmployeeNumber { get; set; } + } + + public class MyAddress + { + public string Street { get; set; } + public AddressType AddressType { get; set; } + } + + public class WorkAddress : MyAddress + { + public string OfficeNumber { get; set; } + } + + public enum AddressType + { + Home, + Work, + Other + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs new file mode 100644 index 000000000..d9b2732e1 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/FakeEdmNodesHelper.cs @@ -0,0 +1,74 @@ +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNetCore.OData.Tests.Query.Expressions +{ + public class FakeSingleEntityNode : SingleEntityNode + { + private readonly IEdmEntityTypeReference typeReference; + private readonly IEdmEntitySetBase set; + + public FakeSingleEntityNode(IEdmEntityTypeReference type, IEdmEntitySetBase set) + { + this.typeReference = type; + this.set = set; + } + + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + public override IEdmNavigationSource NavigationSource + { + get { return this.set; } + } + + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.typeReference; } + } + + public override IEdmEntityTypeReference EntityTypeReference + { + get { return this.typeReference; } + } + + public static FakeSingleEntityNode CreateFakeNodeForPerson() + { + var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person"); + return new FakeSingleEntityNode(EdmModelBuilder.GetEntityTypeReference(personType), EdmModelBuilder.GetPeopleSet()); + } + } + + public class FakeCollectionResourceNode : CollectionResourceNode + { + private readonly IEdmStructuredTypeReference _typeReference; + private readonly IEdmNavigationSource _source; + private readonly IEdmTypeReference _itemType; + private readonly IEdmCollectionTypeReference _collectionType; + + public FakeCollectionResourceNode(IEdmStructuredTypeReference type, IEdmNavigationSource source, IEdmTypeReference itemType, IEdmCollectionTypeReference collectionType) + { + _typeReference = type; + _source = source; + _itemType = itemType; + _collectionType = collectionType; + } + + public override IEdmStructuredTypeReference ItemStructuredType => _typeReference; + + public override IEdmNavigationSource NavigationSource => _source; + + public override IEdmTypeReference ItemType => _itemType; + + public override IEdmCollectionTypeReference CollectionType => _collectionType; + + public static FakeCollectionResourceNode CreateFakeNodeForPerson() + { + var singleEntityNode = FakeSingleEntityNode.CreateFakeNodeForPerson(); + return new FakeCollectionResourceNode( + singleEntityNode.EntityTypeReference, singleEntityNode.NavigationSource, singleEntityNode.EntityTypeReference, singleEntityNode.EntityTypeReference.AsCollection()); + } + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs index e580c2f1f..c53663194 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Expressions/QueryBinderTests.cs @@ -6,13 +6,18 @@ //------------------------------------------------------------------------------ using System; +using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using Microsoft.AspNetCore.OData.Edm; +using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Query.Expressions; using Microsoft.AspNetCore.OData.Query.Wrapper; using Microsoft.AspNetCore.OData.Tests.Commons; using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; using Microsoft.OData.UriParser; +using Microsoft.FullyQualified.NS; using Moq; using Xunit; @@ -100,6 +105,134 @@ public void BindSingleResourceFunctionCallNode_ThrowsNotSupported_ForNotAcceptNo "Unknown function 'anyUnknown'."); } + [Theory] + [InlineData(typeof(ConstantNode))] + [InlineData(typeof(SingleResourceCastNode))] + public void BindSingleResourceFunctionCallNode_CastingEntityType_ReturnsExpression(Type queryNodeType) + { + // Arrange + var binder = new MyQueryBinder(); + + var model = EdmModelBuilder.BuildAndGetEdmModel(); + + // Create the type reference and navigation source + var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person"); + var personTypeRef = EdmModelBuilder.GetEntityTypeReference(personType); + var collectionNode = FakeCollectionResourceNode.CreateFakeNodeForPerson(); + + var employeeType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Employee"); + + // Create a ResourceRangeVariableReferenceNode for the Person entity + var rangeVariable = new ResourceRangeVariable("$it", personTypeRef, collectionNode); + var personNode = new ResourceRangeVariableReferenceNode(rangeVariable.Name, rangeVariable) as SingleValueNode; + + // Create the parameters list + int capacity = 2; + var parameters = new List(capacity) + { + personNode // First parameter is the Person entity + }; + + if (queryNodeType == typeof(SingleResourceCastNode)) + { + // Create a SingleResourceCastNode to cast Person to NS.Employee + var singleResourceCastNode = new SingleResourceCastNode(personNode as SingleResourceNode, employeeType); + parameters.Add(singleResourceCastNode); // Second parameter is the SingleResourceCastNode + } + else if (queryNodeType == typeof(ConstantNode)) + { + // Create a ConstantNode to cast Person to NS.Employee + var constantNode = new ConstantNode("Microsoft.FullyQualified.NS.Employee"); + parameters.Add(constantNode); // Second parameter is the ConstantNode + } + + // Create the SingleResourceFunctionCallNode + var node = new SingleResourceFunctionCallNode("cast", parameters, collectionNode.ItemStructuredType, collectionNode.NavigationSource); + + // Create an instance of QueryBinderContext using the real model and settings + Type clrType = model.GetClrType(personType); + var context = new QueryBinderContext(model, new ODataQuerySettings(), clrType); + + // Act + Expression expression = binder.BindSingleResourceFunctionCallNode(node, context); + + // Assert + Assert.NotNull(expression); + Assert.Equal("($it As Employee)", expression.ToString()); + Assert.Equal("Microsoft.FullyQualified.NS.Employee", expression.Type.ToString()); + Assert.Equal(typeof(Employee), expression.Type); + Assert.Equal(ExpressionType.TypeAs, expression.NodeType); + Assert.Equal(personType.FullName(), (expression as UnaryExpression).Operand.Type.FullName); + Assert.Equal(employeeType.FullName(), (expression as UnaryExpression).Type.FullName); + } + + [Theory] + [InlineData(typeof(ConstantNode))] + [InlineData(typeof(SingleResourceCastNode))] + public void BindSingleResourceFunctionCallNode_PropertyCasting_ReturnsExpression(Type queryNodeType) + { + // Arrange + var binder = new MyQueryBinder(); + + var model = EdmModelBuilder.BuildAndGetEdmModel(); + + // Create the type reference and navigation source + var personType = EdmModelBuilder.GetEntityType("Microsoft.FullyQualified.NS.Person"); + var personTypeRef = EdmModelBuilder.GetEntityTypeReference(personType); + var collectionNode = FakeCollectionResourceNode.CreateFakeNodeForPerson(); + + var myAddressType = EdmModelBuilder.GetEdmComplexType("Microsoft.FullyQualified.NS.MyAddress"); + var workAddressType = EdmModelBuilder.GetEdmComplexType("Microsoft.FullyQualified.NS.WorkAddress"); + + // Create a ResourceRangeVariableReferenceNode for the Person entity + var rangeVariable = new ResourceRangeVariable("$it", personTypeRef, collectionNode); + var personNode = new ResourceRangeVariableReferenceNode(rangeVariable.Name, rangeVariable) as SingleValueNode; + + // Create a SingleComplexNode for the Location property of the Person entity + var locationProperty = EdmModelBuilder.GetPersonLocationProperty(); + var locationNode = new SingleComplexNode(personNode as SingleResourceNode, locationProperty); + + // Create the parameters list + int capacity = 2; + var parameters = new List(capacity) + { + locationNode // First parameter is the Location property + }; + + if(queryNodeType == typeof(SingleResourceCastNode)) + { + // Create a SingleResourceCastNode to cast Location to NS.WorkAddress + var singleResourceCastNode = new SingleResourceCastNode(locationNode, workAddressType); + parameters.Add(singleResourceCastNode); // Second parameter is the SingleResourceCastNode + } + else if (queryNodeType == typeof(ConstantNode)) + { + // Create a ConstantNode to cast Location to NS.WorkAddress + var constantNode = new ConstantNode("Microsoft.FullyQualified.NS.WorkAddress"); + parameters.Add(constantNode); // Second parameter is the ConstantNode + } + + // Create the SingleResourceFunctionCallNode + var node = new SingleResourceFunctionCallNode("cast", parameters, collectionNode.ItemStructuredType, collectionNode.NavigationSource); + + // Create an instance of QueryBinderContext using the real model and settings + Type clrType = model.GetClrType(personType); + var context = new QueryBinderContext(model, new ODataQuerySettings(), clrType); + + // Act + Expression expression = binder.BindSingleResourceFunctionCallNode(node, context); + + // Assert + Assert.NotNull(expression); + Assert.Equal("($it.Location As WorkAddress)", expression.ToString()); + Assert.Equal("Microsoft.FullyQualified.NS.WorkAddress", expression.Type.ToString()); + Assert.Equal("Location", ((expression as UnaryExpression).Operand as MemberExpression).Member.Name); + Assert.Equal(typeof(WorkAddress), expression.Type); + Assert.Equal(ExpressionType.TypeAs, expression.NodeType); + Assert.Equal(workAddressType.FullName(), (expression as UnaryExpression).Type.FullName); + Assert.Equal(myAddressType.FullName(), (expression as UnaryExpression).Operand.Type.FullName); + } + [Fact] public void BindSingleValueFunctionCallNode_ThrowsArgumentNull_ForInputs() {