Merge branch 'odata-v5.3-rtm' into merge.v5.3-rtm

This commit is contained in:
Doug Bunting 2014-10-27 14:02:16 -07:00
Родитель 9c8e948c9c 10e81a6b74
Коммит ae8016bee4
23 изменённых файлов: 5266 добавлений и 253 удалений

Просмотреть файл

@ -25,8 +25,8 @@ using System.Runtime.InteropServices;
#if ASPNETODATA #if ASPNETODATA
#if !BUILD_GENERATED_VERSION #if !BUILD_GENERATED_VERSION
[assembly: AssemblyVersion("5.3.0.0")] // ASPNETODATA [assembly: AssemblyVersion("5.3.1.0")] // ASPNETODATA
[assembly: AssemblyFileVersion("5.3.0.0")] // ASPNETODATA [assembly: AssemblyFileVersion("5.3.1.0")] // ASPNETODATA
#endif #endif
[assembly: AssemblyProduct("Microsoft ASP.NET Web API OData")] [assembly: AssemblyProduct("Microsoft ASP.NET Web API OData")]
#endif #endif

Просмотреть файл

@ -153,6 +153,9 @@ namespace System.Web.Http.OData.Query.Expressions
case QueryNodeKind.EntityCollectionCast: case QueryNodeKind.EntityCollectionCast:
return BindEntityCollectionCastNode(node as EntityCollectionCastNode); return BindEntityCollectionCastNode(node as EntityCollectionCastNode);
case QueryNodeKind.CollectionFunctionCall:
case QueryNodeKind.EntityCollectionFunctionCall:
// Unused or have unknown uses.
default: default:
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
} }
@ -198,6 +201,11 @@ namespace System.Web.Http.OData.Query.Expressions
case QueryNodeKind.SingleEntityCast: case QueryNodeKind.SingleEntityCast:
return BindSingleEntityCastNode(node as SingleEntityCastNode); return BindSingleEntityCastNode(node as SingleEntityCastNode);
case QueryNodeKind.NamedFunctionParameter:
case QueryNodeKind.SingleValueOpenPropertyAccess:
// Unused or have unknown uses.
case QueryNodeKind.SingleEntityFunctionCall:
// Used for some 'cast' calls but not supported here or in FilterQueryValidator.
default: default:
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
} }

Просмотреть файл

@ -70,6 +70,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
ValidateFunction("all", settings);
EnterLambda(settings); EnterLambda(settings);
try try
@ -105,6 +106,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
ValidateFunction("any", settings);
EnterLambda(settings); EnterLambda(settings);
try try
@ -255,7 +257,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
} }
/// <summary> /// <summary>
@ -279,7 +281,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // Validate child nodes but not the ConvertNode itself.
ValidateQueryNode(convertNode.Source, settings); ValidateQueryNode(convertNode.Source, settings);
} }
@ -300,7 +302,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
// recursion // recursion
if (sourceNode != null) if (sourceNode != null)
@ -330,7 +332,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
} }
/// <summary> /// <summary>
@ -354,7 +356,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
ValidateQueryNode(propertyAccessNode.Source, settings); ValidateQueryNode(propertyAccessNode.Source, settings);
} }
@ -379,7 +381,7 @@ namespace System.Web.Http.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
ValidateQueryNode(propertyAccessNode.Source, settings); ValidateQueryNode(propertyAccessNode.Source, settings);
} }
@ -434,6 +436,9 @@ namespace System.Web.Http.OData.Query.Validators
throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators")); throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators"));
} }
break; break;
default:
throw Error.NotSupported(SRResources.UnaryNodeValidationNotSupported, unaryOperatorNode.OperatorKind, typeof(FilterQueryValidator).Name);
} }
} }
@ -547,6 +552,12 @@ namespace System.Web.Http.OData.Query.Validators
case QueryNodeKind.EntityCollectionCast: case QueryNodeKind.EntityCollectionCast:
ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings); ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings);
break; break;
case QueryNodeKind.CollectionFunctionCall:
case QueryNodeKind.EntityCollectionFunctionCall:
// Unused or have unknown uses.
default:
throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name);
} }
} }
@ -607,6 +618,14 @@ namespace System.Web.Http.OData.Query.Validators
case QueryNodeKind.All: case QueryNodeKind.All:
ValidateAllNode(node as AllNode, settings); ValidateAllNode(node as AllNode, settings);
break; break;
case QueryNodeKind.NamedFunctionParameter:
case QueryNodeKind.SingleValueOpenPropertyAccess:
// Unused or have unknown uses.
case QueryNodeKind.SingleEntityFunctionCall:
// Used for some 'cast' calls but not supported here or in FilterBinder.
default:
throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name);
} }
} }
@ -663,7 +682,7 @@ namespace System.Web.Http.OData.Query.Validators
case ClrCanonicalFunctions.IndexofFunctionName: case ClrCanonicalFunctions.IndexofFunctionName:
result = AllowedFunctions.IndexOf; result = AllowedFunctions.IndexOf;
break; break;
case "IsOf": case "isof":
result = AllowedFunctions.IsOf; result = AllowedFunctions.IsOf;
break; break;
case ClrCanonicalFunctions.LengthFunctionName: case ClrCanonicalFunctions.LengthFunctionName:

Просмотреть файл

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.34011 // Runtime Version:4.0.30319.35317
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@ -1383,6 +1383,15 @@ namespace System.Web.Http.OData.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Validating OData QueryNode of kind {0} is not supported by {1}..
/// </summary>
internal static string QueryNodeValidationNotSupported {
get {
return ResourceManager.GetString("QueryNodeValidationNotSupported", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The query parameter &apos;{0}&apos; is not supported.. /// Looks up a localized string similar to The query parameter &apos;{0}&apos; is not supported..
/// </summary> /// </summary>
@ -1626,6 +1635,15 @@ namespace System.Web.Http.OData.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Validating OData UnaryOperatorNode of kind {0} is not supported by {1}..
/// </summary>
internal static string UnaryNodeValidationNotSupported {
get {
return ResourceManager.GetString("UnaryNodeValidationNotSupported", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The element type &apos;{0}&apos; of the given collection type &apos;{1}&apos; is not of the type &apos;{2}&apos;.. /// Looks up a localized string similar to The element type &apos;{0}&apos; of the given collection type &apos;{1}&apos; is not of the type &apos;{2}&apos;..
/// </summary> /// </summary>

Просмотреть файл

@ -201,6 +201,12 @@
<data name="QueryNodeBindingNotSupported" xml:space="preserve"> <data name="QueryNodeBindingNotSupported" xml:space="preserve">
<value>Binding OData QueryNode of kind {0} is not supported by {1}.</value> <value>Binding OData QueryNode of kind {0} is not supported by {1}.</value>
</data> </data>
<data name="QueryNodeValidationNotSupported" xml:space="preserve">
<value>Validating OData QueryNode of kind {0} is not supported by {1}.</value>
</data>
<data name="UnaryNodeValidationNotSupported" xml:space="preserve">
<value>Validating OData UnaryOperatorNode of kind {0} is not supported by {1}.</value>
</data>
<data name="OrderByDuplicateProperty" xml:space="preserve"> <data name="OrderByDuplicateProperty" xml:space="preserve">
<value>Duplicate property named '{0}' is not supported in '$orderby'.</value> <value>Duplicate property named '{0}' is not supported in '$orderby'.</value>
</data> </data>

Просмотреть файл

@ -159,6 +159,11 @@ namespace System.Web.OData.Query.Expressions
case QueryNodeKind.EntityCollectionCast: case QueryNodeKind.EntityCollectionCast:
return BindEntityCollectionCastNode(node as EntityCollectionCastNode); return BindEntityCollectionCastNode(node as EntityCollectionCastNode);
case QueryNodeKind.CollectionFunctionCall:
case QueryNodeKind.EntityCollectionFunctionCall:
case QueryNodeKind.CollectionOpenPropertyAccess:
case QueryNodeKind.CollectionPropertyCast:
// Unused or have unknown uses.
default: default:
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
} }
@ -207,6 +212,14 @@ namespace System.Web.OData.Query.Expressions
case QueryNodeKind.SingleEntityFunctionCall: case QueryNodeKind.SingleEntityFunctionCall:
return BindSingleEntityFunctionCallNode(node as SingleEntityFunctionCallNode); return BindSingleEntityFunctionCallNode(node as SingleEntityFunctionCallNode);
case QueryNodeKind.NamedFunctionParameter:
case QueryNodeKind.SingleValueOpenPropertyAccess:
case QueryNodeKind.ParameterAlias:
case QueryNodeKind.EntitySet:
case QueryNodeKind.KeyLookup:
case QueryNodeKind.SearchTerm:
case QueryNodeKind.SingleValueCast:
// Unused or have unknown uses.
default: default:
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
} }

Просмотреть файл

@ -75,6 +75,7 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
ValidateFunction("all", settings);
EnterLambda(settings); EnterLambda(settings);
try try
@ -110,6 +111,7 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
ValidateFunction("any", settings);
EnterLambda(settings); EnterLambda(settings);
try try
@ -261,7 +263,7 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
} }
/// <summary> /// <summary>
@ -285,7 +287,7 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // Validate child nodes but not the ConvertNode itself.
ValidateQueryNode(convertNode.Source, settings); ValidateQueryNode(convertNode.Source, settings);
} }
@ -312,8 +314,6 @@ namespace System.Web.OData.Query.Validators
throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, navigationProperty.Name)); throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, navigationProperty.Name));
} }
// no default validation logic here
// recursion // recursion
if (sourceNode != null) if (sourceNode != null)
{ {
@ -342,7 +342,7 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // No default validation logic here.
} }
/// <summary> /// <summary>
@ -366,14 +366,13 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// Check whether the property is not filterable // Check whether the property is filterable.
IEdmProperty property = propertyAccessNode.Property; IEdmProperty property = propertyAccessNode.Property;
if (EdmLibHelpers.IsNotFilterable(property, _model)) if (EdmLibHelpers.IsNotFilterable(property, _model))
{ {
throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name)); throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name));
} }
// no default validation logic here
ValidateQueryNode(propertyAccessNode.Source, settings); ValidateQueryNode(propertyAccessNode.Source, settings);
} }
@ -398,7 +397,13 @@ namespace System.Web.OData.Query.Validators
throw Error.ArgumentNull("settings"); throw Error.ArgumentNull("settings");
} }
// no default validation logic here // Check whether the property is filterable.
IEdmProperty property = propertyAccessNode.Property;
if (EdmLibHelpers.IsNotFilterable(property, _model))
{
throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name));
}
ValidateQueryNode(propertyAccessNode.Source, settings); ValidateQueryNode(propertyAccessNode.Source, settings);
} }
@ -431,6 +436,35 @@ namespace System.Web.OData.Query.Validators
} }
} }
/// <summary>
/// Override this method to validate single entity function calls, such as 'cast'.
/// </summary>
/// <param name="node">The node to validate.</param>
/// <param name="settings">The settings to use while validating.</param>
/// <remarks>
/// This method is intended to be called from method overrides in subclasses. This method also supports unit
/// testing scenarios and is not intended to be called from user code. Call the Validate method to validate a
/// <see cref="FilterQueryOption" /> instance.
/// </remarks>
public virtual void ValidateSingleEntityFunctionCallNode(SingleEntityFunctionCallNode node, ODataValidationSettings settings)
{
if (node == null)
{
throw Error.ArgumentNull("node");
}
if (settings == null)
{
throw Error.ArgumentNull("settings");
}
ValidateFunction(node.Name, settings);
foreach (QueryNode argumentNode in node.Parameters)
{
ValidateQueryNode(argumentNode, settings);
}
}
/// <summary> /// <summary>
/// Override this method to validate the Not operator. /// Override this method to validate the Not operator.
/// </summary> /// </summary>
@ -453,6 +487,9 @@ namespace System.Web.OData.Query.Validators
throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators")); throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators"));
} }
break; break;
default:
throw Error.NotSupported(SRResources.UnaryNodeValidationNotSupported, unaryOperatorNode.OperatorKind, typeof(FilterQueryValidator).Name);
} }
} }
@ -566,6 +603,14 @@ namespace System.Web.OData.Query.Validators
case QueryNodeKind.EntityCollectionCast: case QueryNodeKind.EntityCollectionCast:
ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings); ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings);
break; break;
case QueryNodeKind.CollectionFunctionCall:
case QueryNodeKind.EntityCollectionFunctionCall:
case QueryNodeKind.CollectionOpenPropertyAccess:
case QueryNodeKind.CollectionPropertyCast:
// Unused or have unknown uses.
default:
throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name);
} }
} }
@ -610,6 +655,10 @@ namespace System.Web.OData.Query.Validators
ValidateSingleValueFunctionCallNode(node as SingleValueFunctionCallNode, settings); ValidateSingleValueFunctionCallNode(node as SingleValueFunctionCallNode, settings);
break; break;
case QueryNodeKind.SingleEntityFunctionCall:
ValidateSingleEntityFunctionCallNode((SingleEntityFunctionCallNode)node, settings);
break;
case QueryNodeKind.SingleNavigationNode: case QueryNodeKind.SingleNavigationNode:
SingleNavigationNode navigationNode = node as SingleNavigationNode; SingleNavigationNode navigationNode = node as SingleNavigationNode;
ValidateNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty, settings); ValidateNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty, settings);
@ -626,6 +675,17 @@ namespace System.Web.OData.Query.Validators
case QueryNodeKind.All: case QueryNodeKind.All:
ValidateAllNode(node as AllNode, settings); ValidateAllNode(node as AllNode, settings);
break; break;
case QueryNodeKind.NamedFunctionParameter:
case QueryNodeKind.SingleValueOpenPropertyAccess:
case QueryNodeKind.ParameterAlias:
case QueryNodeKind.EntitySet:
case QueryNodeKind.KeyLookup:
case QueryNodeKind.SearchTerm:
case QueryNodeKind.SingleValueCast:
// Unused or have unknown uses.
default:
throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name);
} }
} }
@ -685,7 +745,7 @@ namespace System.Web.OData.Query.Validators
case ClrCanonicalFunctions.IndexofFunctionName: case ClrCanonicalFunctions.IndexofFunctionName:
result = AllowedFunctions.IndexOf; result = AllowedFunctions.IndexOf;
break; break;
case "IsOf": case "isof":
result = AllowedFunctions.IsOf; result = AllowedFunctions.IsOf;
break; break;
case ClrCanonicalFunctions.LengthFunctionName: case ClrCanonicalFunctions.LengthFunctionName:

Просмотреть файл

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.34014 // Runtime Version:4.0.30319.35317
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@ -1563,6 +1563,15 @@ namespace System.Web.OData.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Validating OData QueryNode of kind {0} is not supported by {1}..
/// </summary>
internal static string QueryNodeValidationNotSupported {
get {
return ResourceManager.GetString("QueryNodeValidationNotSupported", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The query parameter &apos;{0}&apos; is not supported.. /// Looks up a localized string similar to The query parameter &apos;{0}&apos; is not supported..
/// </summary> /// </summary>
@ -1869,6 +1878,15 @@ namespace System.Web.OData.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Validating OData UnaryOperatorNode of kind {0} is not supported by {1}..
/// </summary>
internal static string UnaryNodeValidationNotSupported {
get {
return ResourceManager.GetString("UnaryNodeValidationNotSupported", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to The element type &apos;{0}&apos; of the given collection type &apos;{1}&apos; is not of the type &apos;{2}&apos;.. /// Looks up a localized string similar to The element type &apos;{0}&apos; of the given collection type &apos;{1}&apos; is not of the type &apos;{2}&apos;..
/// </summary> /// </summary>

Просмотреть файл

@ -198,6 +198,12 @@
<data name="QueryNodeBindingNotSupported" xml:space="preserve"> <data name="QueryNodeBindingNotSupported" xml:space="preserve">
<value>Binding OData QueryNode of kind {0} is not supported by {1}.</value> <value>Binding OData QueryNode of kind {0} is not supported by {1}.</value>
</data> </data>
<data name="QueryNodeValidationNotSupported" xml:space="preserve">
<value>Validating OData QueryNode of kind {0} is not supported by {1}.</value>
</data>
<data name="UnaryNodeValidationNotSupported" xml:space="preserve">
<value>Validating OData UnaryOperatorNode of kind {0} is not supported by {1}.</value>
</data>
<data name="OrderByDuplicateProperty" xml:space="preserve"> <data name="OrderByDuplicateProperty" xml:space="preserve">
<value>Duplicate property named '{0}' is not supported in '$orderby'.</value> <value>Duplicate property named '{0}' is not supported in '$orderby'.</value>
</data> </data>

Просмотреть файл

@ -0,0 +1,678 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Extensions;
using System.Web.Http.OData.Query;
using Microsoft.Data.Edm;
using Microsoft.TestCommon;
namespace System.Web.Http.OData.Test
{
public class EnableQueryTests
{
// Other not allowed query options like $orderby, $top, etc.
public static TheoryDataSet<string, string> OtherQueryOptionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$orderby=Id", "OrderBy"},
{"?$top=5", "Top"},
{"?$skip=10", "Skip"},
{"?$inlinecount=allpages", "Count"},
{"?$select=Id", "Select"},
{"?$expand=Orders", "Expand"},
};
}
}
// Other unsupported query options
public static TheoryDataSet<string, string> OtherUnsupportedQueryOptionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$format=json", "Format"},
{"?$skiptoken=5", "SkipToken"},
};
}
}
public static TheoryDataSet<string, string> LogicalOperatorsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// And and or operators
{"?$filter=Adult or false", "'Or'"},
{"?$filter=true and Adult", "'And'"},
// Logical operators with simple property
{"?$filter=Id ne 5", "'NotEqual'"},
{"?$filter=Id gt 5", "'GreaterThan'"},
{"?$filter=Id ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Id lt 5", "'LessThan'"},
{"?$filter=Id le 5", "'LessThanOrEqual'"},
// Logical operators with property in a complex type property
{"?$filter=Address/ZipCode ne 5", "'NotEqual'"},
{"?$filter=Address/ZipCode gt 5", "'GreaterThan'"},
{"?$filter=Address/ZipCode ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Address/ZipCode lt 5", "'LessThan'"},
{"?$filter=Address/ZipCode le 5", "'LessThanOrEqual'"},
// Logical operators with property in a single valued navigation property
{"?$filter=Category/Id ne 5", "'NotEqual'"},
{"?$filter=Category/Id gt 5", "'GreaterThan'"},
{"?$filter=Category/Id ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Category/Id lt 5", "'LessThan'"},
{"?$filter=Category/Id le 5", "'LessThanOrEqual'"},
// Logical operators with property in a derived type in a single valued navigation property
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel ne 5", "NotEqual'"},
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel gt 5", "GreaterThan'"},
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel ge 5", "GreaterThanOrEqual'"},
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel lt 5", "LessThan'"},
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel le 5", "LessThanOrEqual'"},
// not operator
{"?$filter=not Adult", "'Not'"},
};
}
}
public static TheoryDataSet<string, string> EqualsOperatorTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=Id eq 5", "Equal"},
{"?$filter=Address/ZipCode eq 5", "Equal"},
{"?$filter=Category/Id eq 5", "Equal"},
{"?$filter=Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel eq 5", "Equal"},
};
}
}
public static TheoryDataSet<string, string> ArithmeticOperatorsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Arithmetic operators with simple property
{"?$filter=1 eq (3 add Id)", "Add"},
{"?$filter=1 eq (3 sub Id)", "Subtract"},
{"?$filter=1 eq (1 mul Id)", "Multiply"},
{"?$filter=1 eq (Id div 1)", "Divide"},
{"?$filter=1 eq (Id mod 1)", "Modulo"},
// Arithmetic operators with property in a complex type property
{"?$filter=1 eq (3 add Address/ZipCode)", "Add"},
{"?$filter=1 eq (3 sub Address/ZipCode)", "Subtract"},
{"?$filter=1 eq (1 mul Address/ZipCode)", "Multiply"},
{"?$filter=1 eq (Address/ZipCode div 1)", "Divide"},
{"?$filter=1 eq (Address/ZipCode mod 1)", "Modulo"},
// Arithmetic operators with property in a single valued navigation property
{"?$filter=1 eq (3 add Category/Id)", "Add"},
{"?$filter=1 eq (3 sub Category/Id)", "Subtract"},
{"?$filter=1 eq (1 mul Category/Id)", "Multiply"},
{"?$filter=1 eq (Category/Id div 1)", "Divide"},
{"?$filter=1 eq (Category/Id mod 1)", "Modulo"},
// Arithmetic operators with property in a derived type in a single valued navigation property
{"?$filter=1 eq (3 add Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Add"},
{"?$filter=1 eq (3 sub Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Subtract"},
{"?$filter=1 eq (1 mul Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Multiply"},
{"?$filter=1 eq (Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel div 1)", "Divide"},
{"?$filter=1 eq (Category/System.Web.Http.OData.Test.PremiumEnableQueryCategory/PremiumLevel mod 1)", "Modulo"},
};
}
}
public static TheoryDataSet<string, string> AnyAndAllFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Primitive collection property
{"?$filter=Points/any()", "any"},
{"?$filter=Points/any(p: p eq 1)", "any"},
{"?$filter=Points/all(p: p eq 1)", "all"},
// Complex type collection property
{"?$filter=Addresses/any()", "any"},
{"?$filter=Addresses/any(a: a/ZipCode eq 1)", "any"},
{"?$filter=Addresses/all(a: a/ZipCode eq 1)", "all"},
// Collection navigation property
{"?$filter=Orders/any()", "any"},
{"?$filter=Orders/any(o: o/Id eq 1)", "any"},
{"?$filter=Orders/all(o: o/Id eq 1)", "all"},
// Collection navigation property with casts
{"?$filter=Orders/any(o: o/System.Web.Http.OData.Test.DiscountedEnableQueryOrder/Discount eq 1)", "any"},
{"?$filter=Orders/all(o: o/System.Web.Http.OData.Test.DiscountedEnableQueryOrder/Discount eq 1)", "all"},
{"?$filter=Orders/System.Web.Http.OData.Test.DiscountedEnableQueryOrder/any()", "any"},
{"?$filter=Orders/System.Web.Http.OData.Test.DiscountedEnableQueryOrder/any(o: o/Discount eq 1)", "any"},
{"?$filter=Orders/System.Web.Http.OData.Test.DiscountedEnableQueryOrder/all(o: o/Discount eq 1)", "all"},
};
}
}
public static TheoryDataSet<string, string> CastFunctionTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Entity type casts
{"?$filter=cast(Category,'System.Web.Http.OData.Test.PremiumEnableQueryCategory') eq null", "cast"},
{"?$filter=cast('System.Web.Http.OData.Test.PremiumEnableQueryCustomer') eq null", "cast"},
};
}
}
public static TheoryDataSet<string, string> IsOfFunctionTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Entity type casts
{"?$filter=isof(Category,'System.Web.Http.OData.Test.PremiumEnableQueryCategory')", "isof"},
{"?$filter=isof('System.Web.Http.OData.Test.PremiumEnableQueryCustomer')", "isof"},
};
}
}
public static TheoryDataSet<string, string> StringFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=startswith(Name, 'Customer')", "startswith"},
{"?$filter=endswith(Name, 'Customer')", "endswith"},
{"?$filter=substringof(Name, 'Customer')", "substringof"},
{"?$filter=length(Name) eq 1", "length"},
{"?$filter=indexof(Name, 'Customer') eq 1", "indexof"},
{"?$filter=concat('Customer', Name) eq 'Customer'", "concat"},
{"?$filter=substring(Name, 3) eq 'Customer'", "substring"},
{"?$filter=substring(Name, 3, 3) eq 'Customer'", "substring"},
{"?$filter=tolower(Name) eq 'customer'", "tolower"},
{"?$filter=toupper(Name) eq 'CUSTOMER'", "toupper"},
{"?$filter=trim(Name) eq 'Customer'", "trim"},
};
}
}
public static TheoryDataSet<string, string> MathFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=round(Id) eq 1", "round"},
{"?$filter=floor(Id) eq 1", "floor"},
{"?$filter=ceiling(Id) eq 1", "ceiling"},
};
}
}
public static TheoryDataSet<string, string> SupportedDateTimeFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=year(AbsoluteBirthDate) eq 1987", "year"},
{"?$filter=month(AbsoluteBirthDate) eq 1987", "month"},
{"?$filter=day(AbsoluteBirthDate) eq 1987", "day"},
{"?$filter=hour(AbsoluteBirthDate) eq 1987", "hour"},
{"?$filter=minute(AbsoluteBirthDate) eq 1987", "minute"},
{"?$filter=second(AbsoluteBirthDate) eq 1987", "second"},
};
}
}
// These represent time functions that we validate but for which we don't support
// end to end.
public static TheoryDataSet<string, string> UnsupportedDateTimeFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=years(Time) eq 1987", "years"},
{"?$filter=months(Time) eq 1987", "months"},
{"?$filter=days(Time) eq 1987", "days"},
{"?$filter=hours(Time) eq 1987", "hours"},
{"?$filter=minutes(Time) eq 1987", "minutes"},
{"?$filter=seconds(Time) eq 1987", "seconds"},
};
}
}
// Other limitations like MaxSkip, MaxTop, AllowedOrderByProperties, etc.
public static TheoryDataSet<string, string> NumericQueryLimitationsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$orderby=Name desc, Id asc", "$orderby"},
{"?$skip=20", "Skip"},
{"?$top=20", "Top"},
{"?$expand=Orders/OrderLines", "$expand"},
{"?$filter=Orders/any(o: o/OrderLines/all(ol: ol/Id gt 0))", "MaxAnyAllExpressionDepth"},
{"?$filter=Orders/any(o: o/Total gt 0) and Id eq 5", "MaxNodeCount"},
};
}
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
[PropertyData("OtherQueryOptionsTestData")]
[PropertyData("OtherUnsupportedQueryOptionsTestData")]
public void EnableQuery_Blocks_NotAllowedQueries(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OnlyFilterAndEqualsAllowedCustomers";
HttpServer server = CreateServer("OnlyFilterAndEqualsAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
public void EnableQuery_BlocksFilter_WhenNotAllowed(string queryString, string unused)
{
// Arrange
string url = "http://localhost/odata/FilterDisabledCustomers";
HttpServer server = CreateServer("FilterDisabledCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains("Filter", errorMessage);
}
[Theory]
[PropertyData("OtherUnsupportedQueryOptionsTestData")]
public void EnableQuery_ReturnsBadRequest_ForUnsupportedQueryOptions(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
// We check equals separately because we need to use it in the rest of the
// tests to produce valid filter expressions in other cases, so we need to
// enable it in those tests and this test only makes sure it covers the case
// when everything is disabled
[Theory]
[PropertyData("EqualsOperatorTestData")]
public void EnableQuery_BlocksEquals_WhenNotAllowed(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OnlyFilterAllowedCustomers";
HttpServer server = CreateServer("OnlyFilterAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("EqualsOperatorTestData")]
[PropertyData("OtherQueryOptionsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
public void EnableQuery_DoesNotBlockQueries_WhenEverythingIsAllowed(string queryString, string unused)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Theory]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
public void EnableQuery_ReturnsBadRequest_ForUnsupportedFunctions(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("unknown function", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("IsOfFunctionTestData")]
[PropertyData("CastFunctionTestData")]
public void EnableQuery_ReturnsInternalServerError_ForIsOfAndCastFunctions(string queryString, string unused)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.Contains("An error has occurred", errorMessage);
}
[Theory]
[PropertyData("NumericQueryLimitationsTestData")]
public void EnableQuery_BlocksQueries_WithOtherLimitations(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OtherLimitationsCustomers";
HttpServer server = CreateServer("OtherLimitationsCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains(expectedElement, errorMessage);
}
// This controller limits any operation except for $filter and the eq operator
// in order to validate that all limitations work.
public class OnlyFilterAndEqualsAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.Equal,
AllowedQueryOptions = AllowedQueryOptions.Filter,
AllowedArithmeticOperators = AllowedArithmeticOperators.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OnlyFilterAndEqualsAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller exposes an action that limits everything except for
// filtering in order to verify that limiting the eq operator works.
// We didn't limit the eq operator in other queries as we need it to
// create valid filter queries using other limited elements and we
// want the query to fail because of limitations imposed on other
// elements rather than eq.
public class OnlyFilterAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.None,
AllowedQueryOptions = AllowedQueryOptions.Filter,
AllowedArithmeticOperators = AllowedArithmeticOperators.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OnlyFilterAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller disables all the query options ($filter amongst them)
public class FilterDisabledCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static FilterDisabledCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller doesn't limit anything specific to ensure that the
// requests succeed if they aren't limited.
public class EverythingAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static EverythingAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller exposes an action that has limitations on aspects
// other than AllowedFunctions, AllowedLogicalOperators, etc.
public class OtherLimitationsCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(MaxNodeCount = 5,
MaxExpansionDepth = 1,
MaxAnyAllExpressionDepth = 1,
MaxSkip = 5,
MaxTop = 5,
MaxOrderByNodeCount = 1)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OtherLimitationsCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
private static HttpServer CreateServer(string customersEntitySet)
{
HttpConfiguration configuration = new HttpConfiguration();
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<EnableQueryCustomer>(customersEntitySet);
builder.Entity<PremiumEnableQueryCustomer>();
builder.EntitySet<EnableQueryCategory>("EnableQueryCategories");
builder.Entity<PremiumEnableQueryCategory>();
builder.EntitySet<EnableQueryOrder>("EnableQueryOrders");
builder.Entity<DiscountedEnableQueryOrder>();
builder.EntitySet<EnableQueryOrderLine>("EnableQueryOrderLines");
builder.ComplexType<EnableQueryAddress>();
IEdmModel model = builder.GetEdmModel();
configuration.Routes.MapODataServiceRoute("odata", "odata", model);
return new HttpServer(configuration);
}
// We need to create the data as we need the queries to succeed in one scenario.
private static IEnumerable<EnableQueryCustomer> CreateCustomers()
{
PremiumEnableQueryCustomer customer = new PremiumEnableQueryCustomer();
customer.Id = 1;
customer.Name = "Customer 1";
customer.Points = Enumerable.Range(1, 10).ToList();
customer.Address = new EnableQueryAddress { ZipCode = 1 };
customer.Addresses = Enumerable.Range(1, 10).Select(j => new EnableQueryAddress { ZipCode = j }).ToList();
customer.Category = new PremiumEnableQueryCategory
{
Id = 1,
PremiumLevel = 1,
};
customer.Orders = Enumerable.Range(1, 10).Select(j => new DiscountedEnableQueryOrder
{
Id = j,
Total = j,
Discount = j,
}).ToList<EnableQueryOrder>();
yield return customer;
}
public class EnableQueryCustomer
{
public int Id { get; set; }
public string Name { get; set; }
public EnableQueryCategory Category { get; set; }
public ICollection<EnableQueryOrder> Orders { get; set; }
public ICollection<int> Points { get; set; }
public ICollection<EnableQueryAddress> Addresses { get; set; }
public EnableQueryAddress Address { get; set; }
public DateTimeOffset AbsoluteBirthDate { get; set; }
public TimeSpan Time { get; set; }
public bool Adult { get; set; }
}
public class PremiumEnableQueryCustomer : EnableQueryCustomer
{
}
public class EnableQueryOrder
{
public int Id { get; set; }
public double Total { get; set; }
public ICollection<EnableQueryOrderLine> OrderLines { get; set; }
}
public class EnableQueryOrderLine
{
public int Id { get; set; }
}
public class DiscountedEnableQueryOrder : EnableQueryOrder
{
public double Discount { get; set; }
}
public class EnableQueryCategory
{
public int Id { get; set; }
}
public class PremiumEnableQueryCategory : EnableQueryCategory
{
public int PremiumLevel { get; set; }
}
public class EnableQueryAddress
{
public int ZipCode { get; set; }
}
}
}

Просмотреть файл

@ -16,6 +16,7 @@ namespace System.Web.Http.OData.Query.Expressions
public int CategoryID { get; set; } public int CategoryID { get; set; }
public string QuantityPerUnit { get; set; } public string QuantityPerUnit { get; set; }
public decimal? UnitPrice { get; set; } public decimal? UnitPrice { get; set; }
public double? Weight { get; set; }
public short? UnitsInStock { get; set; } public short? UnitsInStock { get; set; }
public short? UnitsOnOrder { get; set; } public short? UnitsOnOrder { get; set; }

Просмотреть файл

@ -384,7 +384,7 @@ namespace System.Web.Http.OData.Query.Expressions
var filters = VerifyQueryDeserialization<DataTypes>(filter); var filters = VerifyQueryDeserialization<DataTypes>(filter);
var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value }); var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value });
Assert.Equal(result, expectedResult); Assert.Equal(expectedResult, result);
} }
// Issue: 477 // Issue: 477
@ -1380,6 +1380,649 @@ namespace System.Web.Http.OData.Query.Expressions
#endregion #endregion
#region cast in query option
[Theory]
[InlineData("cast(NoSuchProperty,Edm.Int32) ne null",
"Could not find a property named 'NoSuchProperty' on type 'System.Web.Http.OData.Query.Expressions.DataTypes'.")]
public void Cast_UndefinedSource_ThrowsODataException(string filter, string errorMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage);
}
public static TheoryDataSet<string, string> CastToUnquotedPrimitiveType
{
get
{
return new TheoryDataSet<string, string>
{
{ "cast(Edm.Binary) eq null", "Edm.Binary" },
{ "cast(Edm.Boolean) eq null", "Edm.Boolean" },
{ "cast(Edm.Byte) eq null", "Edm.Byte" },
{ "cast(Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(Edm.DateTimeOffset) eq null", "Edm.DateTimeOffset" },
{ "cast(Edm.Decimal) eq null", "Edm.Decimal" },
{ "cast(Edm.Double) eq null", "Edm.Double" },
{ "cast(Edm.Guid) eq null", "Edm.Guid" },
{ "cast(Edm.Int16) eq null", "Edm.Int16" },
{ "cast(Edm.Int32) eq null", "Edm.Int32" },
{ "cast(Edm.Int64) eq null", "Edm.Int64" },
{ "cast(Edm.SByte) eq null", "Edm.SByte" },
{ "cast(Edm.Single) eq null", "Edm.Single" },
{ "cast(Edm.Stream) eq null", "Edm.Stream" },
{ "cast(Edm.String) eq null", "Edm.String" },
{ "cast(Edm.Time) eq null", "Edm.Time" },
{ "cast(Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast(null,Edm.Binary) eq null", "Edm.Binary" },
{ "cast(null,Edm.Boolean) eq null", "Edm.Boolean" },
{ "cast(null,Edm.Byte) eq null", "Edm.Byte" },
{ "cast(null,Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(null,Edm.DateTimeOffset) eq null", "Edm.DateTimeOffset" },
{ "cast(null,Edm.Decimal) eq null", "Edm.Decimal" },
{ "cast(null,Edm.Double) eq null", "Edm.Double" },
{ "cast(null,Edm.Guid) eq null", "Edm.Guid" },
{ "cast(null,Edm.Int16) eq null", "Edm.Int16" },
{ "cast(null,Edm.Int32) eq null", "Edm.Int32" },
{ "cast(null,Edm.Int64) eq null", "Edm.Int64" },
{ "cast(null,Edm.SByte) eq null", "Edm.SByte" },
{ "cast(null,Edm.Single) eq null", "Edm.Single" },
{ "cast(null,Edm.Stream) eq null", "Edm.Stream" },
{ "cast(null,Edm.String) eq null", "Edm.String" },
{ "cast(null,Edm.Time) eq null", "Edm.Time" },
{ "cast(null,Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast(binary'4F64617461',Edm.Binary) eq null", "Edm.Binary" },
{ "cast(false,Edm.Boolean) eq binary'4F64617461'", "Edm.Boolean" },
{ "cast(23,Edm.Byte) eq 23", "Edm.Byte" },
{ "cast(datetime'2001-01-01T12:00:00.000',Edm.DateTime) eq datetime'2001-01-01T12:00:00.000'", "Edm.DateTime" },
{ "cast(datetimeoffset'2001-01-01T12:00:00.000+08:00',Edm.DateTimeOffset) eq datetimeoffset'2001-01-01T12:00:00.000+08:00'", "Edm.DateTimeOffset" },
{ "cast(23,Edm.Decimal) eq 23", "Edm.Decimal" },
{ "cast(23,Edm.Double) eq 23", "Edm.Double" },
{ "cast(guid'00000000-0000-0000-0000-000000000000',Edm.Guid) eq guid'00000000-0000-0000-0000-000000000000'", "Edm.Guid" },
{ "cast(23,Edm.Int16) eq 23", "Edm.Int16" },
{ "cast(23,Edm.Int32) eq 23", "Edm.Int32" },
{ "cast(23,Edm.Int64) eq 23", "Edm.Int64" },
{ "cast(23,Edm.SByte) eq 23", "Edm.SByte" },
{ "cast(23,Edm.Single) eq 23", "Edm.Single" },
{ "cast('hello',Edm.Stream) eq null", "Edm.Stream" },
{ "cast('hello',Edm.String) eq 'hello'", "Edm.String" },
{ "cast(time'PT12H',Edm.Time) eq time'PT12H'", "Edm.Time" },
{ "cast('',Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast('OData',Edm.Binary) eq binary'4F64617461'", "Edm.Binary" },
{ "cast('false',Edm.Boolean) eq false", "Edm.Boolean" },
{ "cast('23',Edm.Byte) eq 23", "Edm.Byte" },
{ "cast('2001-01-01T12:00:00.000',Edm.DateTime) eq datetime'2001-01-01T12:00:00.000'", "Edm.DateTime" },
{ "cast('2001-01-01T12:00:00.000+08:00',Edm.DateTimeOffset) eq datetimeoffset'2001-01-01T12:00:00.000+08:00'", "Edm.DateTimeOffset" },
{ "cast('23',Edm.Decimal) eq 23", "Edm.Decimal" },
{ "cast('23',Edm.Double) eq 23", "Edm.Double" },
{ "cast('00000000-0000-0000-0000-000000000000',Edm.Guid) eq guid'00000000-0000-0000-0000-000000000000'", "Edm.Guid" },
{ "cast('23',Edm.Int16) eq 23", "Edm.Int16" },
{ "cast('23',Edm.Int32) eq 23", "Edm.Int32" },
{ "cast('23',Edm.Int64) eq 23", "Edm.Int64" },
{ "cast('23',Edm.SByte) eq 23", "Edm.SByte" },
{ "cast('23',Edm.Single) eq 23", "Edm.Single" },
{ "cast(23,Edm.String) eq '23'", "Edm.String" },
{ "cast('PT12H',Edm.Time) eq time'PT12H'", "Edm.Time" },
{ "cast(ByteArrayProp,Edm.Binary) eq null", "Edm.Binary" },
{ "cast(DateTimeProp,Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(DateTimeOffsetProp,Edm.DateTimeOffset) eq null", "Edm.DateTimeOffset" },
{ "cast(DecimalProp,Edm.Decimal) eq null", "Edm.Decimal" },
{ "cast(DoubleProp,Edm.Double) eq null", "Edm.Double" },
{ "cast(GuidProp,Edm.Guid) eq null", "Edm.Guid" },
{ "cast(NullableShortProp,Edm.Int16) eq null", "Edm.Int16" },
{ "cast(IntProp,Edm.Int32) eq null", "Edm.Int32" },
{ "cast(LongProp,Edm.Int64) eq null", "Edm.Int64" },
{ "cast(FloatProp,Edm.Single) eq null", "Edm.Single" },
{ "cast(StringProp,Edm.String) eq null", "Edm.String" },
};
}
}
[Theory]
[PropertyData("CastToUnquotedPrimitiveType")]
public void CastToUnquotedPrimitiveType_ThrowsODataException(string filter, string typeName)
{
// Arrange
var expectedMessage = string.Format(
"The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types.",
typeName);
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> CastToQuotedPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "cast('Edm.Binary') eq null" },
{ "cast('Edm.Boolean') eq null" },
{ "cast('Edm.Byte') eq null" },
{ "cast('Edm.DateTime') eq null" },
{ "cast('Edm.DateTimeOffset') eq null" },
{ "cast('Edm.Decimal') eq null" },
{ "cast('Edm.Double') eq null" },
{ "cast('Edm.Guid') eq null" },
{ "cast('Edm.Int16') eq null" },
{ "cast('Edm.Int32') eq null" },
{ "cast('Edm.Int64') eq null" },
{ "cast('Edm.SByte') eq null" },
{ "cast('Edm.Single') eq null" },
{ "cast('Edm.String') eq null" },
{ "cast('Edm.Time') eq null" },
{ "cast(null,'Edm.Binary') eq null" },
{ "cast(null,'Edm.Boolean') eq null" },
{ "cast(null,'Edm.Byte') eq null" },
{ "cast(null,'Edm.DateTime') eq null" },
{ "cast(null,'Edm.DateTimeOffset') eq null" },
{ "cast(null,'Edm.Decimal') eq null" },
{ "cast(null,'Edm.Double') eq null" },
{ "cast(null,'Edm.Guid') eq null" },
{ "cast(null,'Edm.Int16') eq null" },
{ "cast(null,'Edm.Int32') eq null" },
{ "cast(null,'Edm.Int64') eq null" },
{ "cast(null,'Edm.SByte') eq null" },
{ "cast(null,'Edm.Single') eq null" },
{ "cast(null,'Edm.String') eq null" },
{ "cast(null,'Edm.Time') eq null" },
{ "cast(binary'4F64617461','Edm.Binary') eq binary'4F64617461'" },
{ "cast(false,'Edm.Boolean') eq false" },
{ "cast(23,'Edm.Byte') eq 23" },
{ "cast(datetime'2001-01-01T12:00:00.000','Edm.DateTime') eq datetime'2001-01-01T12:00:00.000'" },
{ "cast(datetimeoffset'2001-01-01T12:00:00.000+08:00','Edm.DateTimeOffset') eq datetimeoffset'2001-01-01T12:00:00.000+08:00'" },
{ "cast(23,'Edm.Decimal') eq 23" },
{ "cast(23,'Edm.Double') eq 23" },
{ "cast(guid'00000000-0000-0000-0000-000000000000','Edm.Guid') eq guid'00000000-0000-0000-0000-000000000000'" },
{ "cast(23,'Edm.Int16') eq 23" },
{ "cast(23,'Edm.Int32') eq 23" },
{ "cast(23,'Edm.Int64') eq 23" },
{ "cast(23,'Edm.SByte') eq 23" },
{ "cast(23,'Edm.Single') eq 23" },
{ "cast('hello','Edm.String') eq 'hello'" },
{ "cast(time'PT12H','Edm.Time') eq time'PT12H'" },
{ "cast('OData','Edm.Binary') eq binary'4F64617461'" },
{ "cast('false','Edm.Boolean') eq false" },
{ "cast('23','Edm.Byte') eq 23" },
{ "cast('2001-01-01T12:00:00.000','Edm.DateTime') eq datetime'2001-01-01T12:00:00.000'" },
{ "cast('2001-01-01T12:00:00.000+08:00','Edm.DateTimeOffset') eq datetimeoffset'2001-01-01T12:00:00.000+08:00'" },
{ "cast('23','Edm.Decimal') eq 23" },
{ "cast('23','Edm.Double') eq 23" },
{ "cast('00000000-0000-0000-0000-000000000000','Edm.Guid') eq guid'00000000-0000-0000-0000-000000000000'" },
{ "cast('23','Edm.Int16') eq 23" },
{ "cast('23','Edm.Int32') eq 23" },
{ "cast('23','Edm.Int64') eq 23" },
{ "cast('23','Edm.SByte') eq 23" },
{ "cast('23','Edm.Single') eq 23" },
{ "cast(23,'Edm.String') eq '23'" },
{ "cast('PT12H','Edm.Time') eq time'PT12H'" },
{ "cast(ByteArrayProp,'Edm.Binary') eq null" },
{ "cast(DateTimeProp,'Edm.DateTime') eq datetime'2001-01-01T12:00:00.000'" },
{ "cast(DateTimeOffsetProp,'Edm.DateTimeOffset') eq datetimeoffset'2001-01-01T12:00:00.000+08:00'" },
{ "cast(DecimalProp,'Edm.Decimal') eq 23" },
{ "cast(DoubleProp,'Edm.Double') eq 23" },
{ "cast(GuidProp,'Edm.Guid') eq guid'0EFDAECF-A9F0-42F3-A384-1295917AF95E'" },
{ "cast(NullableShortProp,'Edm.Int16') eq 23" },
{ "cast(IntProp,'Edm.Int32') eq 23" },
{ "cast(LongProp,'Edm.Int64') eq 23" },
{ "cast(FloatProp,'Edm.Single') eq 23" },
{ "cast(StringProp,'Edm.String') eq 'hello'" },
{ "cast(TimeSpanProp,'Edm.Time') eq time'PT23H'" },
};
}
}
// Exception messages here is misleading since issue is actually quoting the type name.
[Theory]
[PropertyData("CastToQuotedPrimitiveType")]
public void CastToQuotedPrimitiveType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'cast'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> CastToUnquotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "cast(System.Web.Http.OData.Query.Expressions.Address) eq null" },
{ "cast(null, System.Web.Http.OData.Query.Expressions.Address) eq null" },
{ "cast('', System.Web.Http.OData.Query.Expressions.Address) eq null" },
{ "cast(SupplierAddress, System.Web.Http.OData.Query.Expressions.Address) eq null" },
};
}
}
[Theory]
[PropertyData("CastToUnquotedComplexType")]
public void CastToUnquotedComplexType_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage =
"The child type 'System.Web.Http.OData.Query.Expressions.Address' in a cast was not an entity type. " +
"Casts can only be performed on entity types.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string> CastToQuotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "cast('System.Web.Http.OData.Query.Expressions.Address') eq null" },
{ "cast(null, 'System.Web.Http.OData.Query.Expressions.Address') eq null" },
{ "cast('', 'System.Web.Http.OData.Query.Expressions.Address') eq null" },
{ "cast(SupplierAddress, 'System.Web.Http.OData.Query.Expressions.Address') eq null" },
};
}
}
[Theory]
[PropertyData("CastToQuotedComplexType")]
public void CastToQuotedComplexType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'cast'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string, string> CastToUnquotedEntityType
{
get
{
return new TheoryDataSet<string, string>
{
{
"cast(System.Web.Http.OData.Query.Expressions.DerivedProduct)/DerivedProductName eq null",
"Cast or IsOf Function must have a type in its arguments."
},
{
"cast(null, System.Web.Http.OData.Query.Expressions.DerivedCategory)/DerivedCategoryName eq null",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
{
"cast(Category, System.Web.Http.OData.Query.Expressions.DerivedCategory)/DerivedCategoryName eq null",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
};
}
}
[Theory]
[PropertyData("CastToUnquotedEntityType")]
public void CastToUnquotedEntityType_ThrowsODataException(string filter, string expectedMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
[Theory]
[InlineData("cast('System.Web.Http.OData.Query.Expressions.DerivedProduct')/DerivedProductName eq null")]
[InlineData("cast(Category,'System.Web.Http.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
[InlineData("cast(Category, 'System.Web.Http.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
public void CastToQuotedEntityType_ThrowsNotSupported(string filter)
{
// Arrange
var expectedMessage = "Binding OData QueryNode of kind SingleEntityFunctionCall is not supported by FilterBinder.";
// Act & Assert
Assert.Throws<NotSupportedException>(() => Bind<Product>(filter), expectedMessage);
}
[Theory]
[InlineData("cast(null,'System.Web.Http.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
[InlineData("cast(null, 'System.Web.Http.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
public void CastNullToQuotedEntityType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'cast'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<Product>(filter), expectedMessage);
}
#endregion
#region 'isof' in query option
[Theory]
[InlineData("isof(NoSuchProperty,Edm.Int32)",
"Could not find a property named 'NoSuchProperty' on type 'System.Web.Http.OData.Query.Expressions.DataTypes'.")]
public void IsOfUndefinedSource_ThrowsODataException(string filter, string errorMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage);
}
public static TheoryDataSet<string, string> IsOfUnquotedPrimitiveType
{
get
{
return new TheoryDataSet<string, string>
{
{ "isof(Edm.Binary)", "Edm.Binary" },
{ "isof(Edm.Boolean)", "Edm.Boolean" },
{ "isof(Edm.Byte)", "Edm.Byte" },
{ "isof(Edm.DateTime)", "Edm.DateTime" },
{ "isof(Edm.DateTimeOffset)", "Edm.DateTimeOffset" },
{ "isof(Edm.Decimal)", "Edm.Decimal" },
{ "isof(Edm.Double)", "Edm.Double" },
{ "isof(Edm.Guid)", "Edm.Guid" },
{ "isof(Edm.Int16)", "Edm.Int16" },
{ "isof(Edm.Int32)", "Edm.Int32" },
{ "isof(Edm.Int64)", "Edm.Int64" },
{ "isof(Edm.SByte)", "Edm.SByte" },
{ "isof(Edm.Single)", "Edm.Single" },
{ "isof(Edm.Stream)", "Edm.Stream" },
{ "isof(Edm.String)", "Edm.String" },
{ "isof(Edm.Time)", "Edm.Time" },
{ "isof(Edm.Unknown)", "Edm.Unknown" },
{ "isof(null,Edm.Binary)", "Edm.Binary" },
{ "isof(null,Edm.Boolean)", "Edm.Boolean" },
{ "isof(null,Edm.Byte)", "Edm.Byte" },
{ "isof(null,Edm.DateTime)", "Edm.DateTime" },
{ "isof(null,Edm.DateTimeOffset)", "Edm.DateTimeOffset" },
{ "isof(null,Edm.Decimal)", "Edm.Decimal" },
{ "isof(null,Edm.Double)", "Edm.Double" },
{ "isof(null,Edm.Guid)", "Edm.Guid" },
{ "isof(null,Edm.Int16)", "Edm.Int16" },
{ "isof(null,Edm.Int32)", "Edm.Int32" },
{ "isof(null,Edm.Int64)", "Edm.Int64" },
{ "isof(null,Edm.SByte)", "Edm.SByte" },
{ "isof(null,Edm.Single)", "Edm.Single" },
{ "isof(null,Edm.Stream)", "Edm.Stream" },
{ "isof(null,Edm.String)", "Edm.String" },
{ "isof(null,Edm.Time)", "Edm.Time" },
{ "isof(null,Edm.Unknown)", "Edm.Unknown" },
{ "isof(binary'4F64617461',Edm.Binary)", "Edm.Binary" },
{ "isof(false,Edm.Boolean)", "Edm.Boolean" },
{ "isof(23,Edm.Byte)", "Edm.Byte" },
{ "isof(datetime'2001-01-01T12:00:00.000',Edm.DateTime)", "Edm.DateTime" },
{ "isof(datetimeoffset'2001-01-01T12:00:00.000+08:00',Edm.DateTimeOffset)", "Edm.DateTimeOffset" },
{ "isof(23,Edm.Decimal)", "Edm.Decimal" },
{ "isof(23,Edm.Double)", "Edm.Double" },
{ "isof(guid'00000000-0000-0000-0000-000000000000',Edm.Guid)", "Edm.Guid" },
{ "isof(23,Edm.Int16)", "Edm.Int16" },
{ "isof(23,Edm.Int32)", "Edm.Int32" },
{ "isof(23,Edm.Int64)", "Edm.Int64" },
{ "isof(23,Edm.SByte)", "Edm.SByte" },
{ "isof(23,Edm.Single)", "Edm.Single" },
{ "isof('hello',Edm.Stream)", "Edm.Stream" },
{ "isof('hello',Edm.String)", "Edm.String" },
{ "isof(time'PT12H',Edm.Time)", "Edm.Time" },
{ "isof('',Edm.Unknown)", "Edm.Unknown" },
{ "isof('OData',Edm.Binary)", "Edm.Binary" },
{ "isof('false',Edm.Boolean)", "Edm.Boolean" },
{ "isof('23',Edm.Byte)", "Edm.Byte" },
{ "isof('2001-01-01T12:00:00.000',Edm.DateTime)", "Edm.DateTime" },
{ "isof('2001-01-01T12:00:00.000+08:00',Edm.DateTimeOffset)", "Edm.DateTimeOffset" },
{ "isof('23',Edm.Decimal)", "Edm.Decimal" },
{ "isof('23',Edm.Double)", "Edm.Double" },
{ "isof('00000000-0000-0000-0000-000000000000',Edm.Guid)", "Edm.Guid" },
{ "isof('23',Edm.Int16)", "Edm.Int16" },
{ "isof('23',Edm.Int32)", "Edm.Int32" },
{ "isof('23',Edm.Int64)", "Edm.Int64" },
{ "isof('23',Edm.SByte)", "Edm.SByte" },
{ "isof('23',Edm.Single)", "Edm.Single" },
{ "isof(23,Edm.String)", "Edm.String" },
{ "isof('PT12H',Edm.Time)", "Edm.Time" },
{ "isof(ByteArrayProp,Edm.Binary)", "Edm.Binary" },
{ "isof(DateTimeProp,Edm.DateTime)", "Edm.DateTime" },
{ "isof(DateTimeOffsetProp,Edm.DateTimeOffset)", "Edm.DateTimeOffset" },
{ "isof(DecimalProp,Edm.Decimal)", "Edm.Decimal" },
{ "isof(DoubleProp,Edm.Double)", "Edm.Double" },
{ "isof(GuidProp,Edm.Guid)", "Edm.Guid" },
{ "isof(NullableShortProp,Edm.Int16)", "Edm.Int16" },
{ "isof(IntProp,Edm.Int32)", "Edm.Int32" },
{ "isof(LongProp,Edm.Int64)", "Edm.Int64" },
{ "isof(FloatProp,Edm.Single)", "Edm.Single" },
{ "isof(StringProp,Edm.String)", "Edm.String" },
{ "isof(TimeSpanProp,Edm.Time)", "Edm.Time" },
{ "isof(IntProp,Edm.Unknown)", "Edm.Unknown" },
};
}
}
[Theory]
[PropertyData("IsOfUnquotedPrimitiveType")]
public void IsOfUnquotedPrimitiveType_ThrowsODataException(string filter, string typeName)
{
// Arrange
var expectedMessage = string.Format(
"The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types.",
typeName);
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfQuotedPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "isof('Edm.Binary')" },
{ "isof('Edm.Boolean')" },
{ "isof('Edm.Byte')" },
{ "isof('Edm.DateTime')" },
{ "isof('Edm.DateTimeOffset')" },
{ "isof('Edm.Decimal')" },
{ "isof('Edm.Double')" },
{ "isof('Edm.Guid')" },
{ "isof('Edm.Int16')" },
{ "isof('Edm.Int32')" },
{ "isof('Edm.Int64')" },
{ "isof('Edm.SByte')" },
{ "isof('Edm.Single')" },
{ "isof('Edm.String')" },
{ "isof('Edm.Time')" },
{ "isof(null,'Edm.Binary')" },
{ "isof(null,'Edm.Boolean')" },
{ "isof(null,'Edm.Byte')" },
{ "isof(null,'Edm.DateTime')" },
{ "isof(null,'Edm.DateTimeOffset')" },
{ "isof(null,'Edm.Decimal')" },
{ "isof(null,'Edm.Double')" },
{ "isof(null,'Edm.Guid')" },
{ "isof(null,'Edm.Int16')" },
{ "isof(null,'Edm.Int32')" },
{ "isof(null,'Edm.Int64')" },
{ "isof(null,'Edm.SByte')" },
{ "isof(null,'Edm.Single')" },
{ "isof(null,'Edm.Stream')" },
{ "isof(null,'Edm.String')" },
{ "isof(null,'Edm.Time')" },
{ "isof(binary'4F64617461','Edm.Binary')" },
{ "isof(false,'Edm.Boolean')" },
{ "isof(23,'Edm.Byte')" },
{ "isof(datetime'2001-01-01T12:00:00.000+08:00','Edm.DateTime')" },
{ "isof(datetimeoffset'2001-01-01T12:00:00.000+08:00','Edm.DateTimeOffset')" },
{ "isof(23,'Edm.Decimal')" },
{ "isof(23,'Edm.Double')" },
{ "isof(guid'00000000-0000-0000-0000-000000000000','Edm.Guid')" },
{ "isof(23,'Edm.Int16')" },
{ "isof(23,'Edm.Int32')" },
{ "isof(23,'Edm.Int64')" },
{ "isof(23,'Edm.SByte')" },
{ "isof(23,'Edm.Single')" },
{ "isof('hello','Edm.Stream')" },
{ "isof('hello','Edm.String')" },
{ "isof(time'PT12H','Edm.Time')" },
{ "isof('OData','Edm.Binary')" },
{ "isof('false','Edm.Boolean')" },
{ "isof('23','Edm.Byte')" },
{ "isof('2001-01-01T12:00:00.000+08:00','Edm.DateTime')" },
{ "isof('2001-01-01T12:00:00.000+08:00','Edm.DateTimeOffset')" },
{ "isof('23','Edm.Decimal')" },
{ "isof('23','Edm.Double')" },
{ "isof('00000000-0000-0000-0000-000000000000','Edm.Guid')" },
{ "isof('23','Edm.Int16')" },
{ "isof('23','Edm.Int32')" },
{ "isof('23','Edm.Int64')" },
{ "isof('23','Edm.SByte')" },
{ "isof('23','Edm.Single')" },
{ "isof(23,'Edm.String')" },
{ "isof('PT12H','Edm.Time')" },
{ "isof(ByteArrayProp,'Edm.Binary')" },
{ "isof(DateTimeProp,'Edm.DateTime')" },
{ "isof(DateTimeOffsetProp,'Edm.DateTimeOffset')" },
{ "isof(DecimalProp,'Edm.Decimal')" },
{ "isof(DoubleProp,'Edm.Double')" },
{ "isof(GuidProp,'Edm.Guid')" },
{ "isof(NullableShortProp,'Edm.Int16')" },
{ "isof(IntProp,'Edm.Int32')" },
{ "isof(LongProp,'Edm.Int64')" },
{ "isof(FloatProp,'Edm.Single')" },
{ "isof(StringProp,'Edm.String')" },
{ "isof(TimeSpanProp,'Edm.Time')" },
};
}
}
[Theory]
[PropertyData("IsOfQuotedPrimitiveType")]
public void IsOfQuotedPrimitiveType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'isof'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfUnquotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "isof(System.Web.Http.OData.Query.Expressions.Address)" },
{ "isof(null,System.Web.Http.OData.Query.Expressions.Address)" },
{ "isof(null, System.Web.Http.OData.Query.Expressions.Address)" },
{ "isof(SupplierAddress,System.Web.Http.OData.Query.Expressions.Address)" },
{ "isof(SupplierAddress, System.Web.Http.OData.Query.Expressions.Address)" },
};
}
}
[Theory]
[PropertyData("IsOfUnquotedComplexType")]
public void IsOfUnquotedComplexType_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage =
"The child type 'System.Web.Http.OData.Query.Expressions.Address' in a cast was not an entity type. " +
"Casts can only be performed on entity types.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string, string> IsOfUnquotedEntityType
{
get
{
return new TheoryDataSet<string, string>
{
{
"isof(System.Web.Http.OData.Query.Expressions.DerivedProduct)",
"Cast or IsOf Function must have a type in its arguments."
},
{
"isof(null,System.Web.Http.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
{
"isof(null, System.Web.Http.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
{
"isof(Category,System.Web.Http.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
{
"isof(Category, System.Web.Http.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.Http.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.Http.OData.Query.Expressions.Product'."
},
};
}
}
[Theory]
[PropertyData("IsOfUnquotedEntityType")]
public void IsOfUnquotedEntityType_ThrowsODataException(string filter, string expectedMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfQuotedNonPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "isof('System.Web.Http.OData.Query.Expressions.Address')" },
{ "isof('System.Web.Http.OData.Query.Expressions.DerivedProduct')" },
{ "isof(null,'System.Web.Http.OData.Query.Expressions.Address')" },
{ "isof(null, 'System.Web.Http.OData.Query.Expressions.Address')" },
{ "isof(null,'System.Web.Http.OData.Query.Expressions.DerivedCategory')" },
{ "isof(null, 'System.Web.Http.OData.Query.Expressions.DerivedCategory')" },
{ "isof(SupplierAddress,'System.Web.Http.OData.Query.Expressions.Address')" },
{ "isof(SupplierAddress, 'System.Web.Http.OData.Query.Expressions.Address')" },
{ "isof(Category,'System.Web.Http.OData.Query.Expressions.DerivedCategory')" },
{ "isof(Category, 'System.Web.Http.OData.Query.Expressions.DerivedCategory')" },
};
}
}
[Theory]
[PropertyData("IsOfQuotedNonPrimitiveType")]
public void IsOfQuotedNonPrimitiveType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'isof'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<Product>(filter), expectedMessage);
}
#endregion
[Theory] [Theory]
[InlineData("UShortProp eq 12", "$it => (Convert($it.UShortProp) == 12)")] [InlineData("UShortProp eq 12", "$it => (Convert($it.UShortProp) == 12)")]
[InlineData("ULongProp eq 12L", "$it => (Convert($it.ULongProp) == 12)")] [InlineData("ULongProp eq 12L", "$it => (Convert($it.ULongProp) == 12)")]
@ -1542,7 +2185,7 @@ namespace System.Web.Http.OData.Query.Expressions
} }
else else
{ {
Assert.Equal(RunFilter(filterWithNullPropagation, product), expectedValue.WithNullPropagation); Assert.Equal(expectedValue.WithNullPropagation, RunFilter(filterWithNullPropagation, product));
} }
var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>; var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>;
@ -1552,7 +2195,7 @@ namespace System.Web.Http.OData.Query.Expressions
} }
else else
{ {
Assert.Equal(RunFilter(filterWithoutNullPropagation, product), expectedValue.WithoutNullPropagation); Assert.Equal(expectedValue.WithoutNullPropagation, RunFilter(filterWithoutNullPropagation, product));
} }
} }
@ -1631,6 +2274,12 @@ namespace System.Web.Http.OData.Query.Expressions
{ {
ODataModelBuilder model = new ODataConventionModelBuilder(); ODataModelBuilder model = new ODataConventionModelBuilder();
model.EntitySet<T>("Products"); model.EntitySet<T>("Products");
if (key == typeof(Product))
{
model.Entity<DerivedProduct>().DerivesFrom<Product>();
model.Entity<DerivedCategory>().DerivesFrom<Category>();
}
value = _modelCache[key] = model.GetEdmModel(); value = _modelCache[key] = model.GetEdmModel();
} }
return value; return value;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,5 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using Microsoft.Data.OData; using Microsoft.Data.OData;
using Microsoft.TestCommon; using Microsoft.TestCommon;
@ -18,6 +20,35 @@ namespace System.Web.Http.OData.Query.Validators
_context = ValidationTestHelper.CreateCustomerContext(); _context = ValidationTestHelper.CreateCustomerContext();
} }
public static TheoryDataSet<AllowedQueryOptions, string, string> SupportedQueryOptions
{
get
{
return new TheoryDataSet<AllowedQueryOptions, string, string>
{
{ AllowedQueryOptions.Expand, "$expand=Contacts", "Expand" },
{ AllowedQueryOptions.Filter, "$filter=Name eq 'Name'", "Filter" },
{ AllowedQueryOptions.InlineCount, "$inlinecount=allpages", "InlineCount" },
{ AllowedQueryOptions.OrderBy, "$orderby=Name", "OrderBy" },
{ AllowedQueryOptions.Select, "$select=Name", "Select" },
{ AllowedQueryOptions.Skip, "$skip=5", "Skip" },
{ AllowedQueryOptions.Top, "$top=10", "Top" },
};
}
}
public static TheoryDataSet<AllowedQueryOptions, string, string> UnsupportedQueryOptions
{
get
{
return new TheoryDataSet<AllowedQueryOptions, string, string>
{
{ AllowedQueryOptions.Format, "$format=json", "Format" },
{ AllowedQueryOptions.SkipToken, "$skiptoken=__skip__", "SkipToken" },
};
}
}
[Fact] [Fact]
public void ValidateThrowsOnNullOption() public void ValidateThrowsOnNullOption()
{ {
@ -32,64 +63,171 @@ namespace System.Web.Http.OData.Query.Validators
_validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null)); _validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null));
} }
[Theory] [Fact]
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)] public void QueryOptionDataSets_CoverAllValues()
[InlineData("orderby", "Name", AllowedQueryOptions.OrderBy)]
[InlineData("skip", "5", AllowedQueryOptions.Skip)]
[InlineData("top", "5", AllowedQueryOptions.Top)]
[InlineData("inlinecount", "none", AllowedQueryOptions.InlineCount)]
[InlineData("select", "Name", AllowedQueryOptions.Select)]
[InlineData("expand", "Contacts", AllowedQueryOptions.Expand)]
[InlineData("format", "json", AllowedQueryOptions.Format)]
[InlineData("skiptoken", "token", AllowedQueryOptions.SkipToken)]
public void Validate_Throws_ForDisallowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
{ {
// Arrange // Arrange
HttpRequestMessage message = new HttpRequestMessage( // Get all values in the AllowedQueryOptions enum.
HttpMethod.Get, var values = new HashSet<AllowedQueryOptions>(
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue) Enum.GetValues(typeof(AllowedQueryOptions)).Cast<AllowedQueryOptions>());
);
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~queryOption
};
// Act & Assert var groupValues = new[]
var exception = Assert.Throws<ODataException>(() => _validator.Validate(option, settings)); {
Assert.Equal( AllowedQueryOptions.All,
"Query option '" + queryOptionName + "' is not allowed. To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.", AllowedQueryOptions.None,
exception.Message, AllowedQueryOptions.Supported,
StringComparer.OrdinalIgnoreCase); };
var dataSets = SupportedQueryOptions.Concat(UnsupportedQueryOptions);
// Act
// Remove the group items.
foreach (var allowed in groupValues)
{
values.Remove(allowed);
}
// Remove the individual items.
foreach (var allowed in dataSets.Select(item => (AllowedQueryOptions)(item[0])))
{
values.Remove(allowed);
}
// Assert
// Should have nothing left.
Assert.Empty(values);
} }
[Theory] [Theory]
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)] [PropertyData("SupportedQueryOptions")]
[InlineData("orderby", "Name", AllowedQueryOptions.OrderBy)] [PropertyData("UnsupportedQueryOptions")]
[InlineData("skip", "5", AllowedQueryOptions.Skip)] public void AllowedQueryOptions_SucceedIfAllowed(AllowedQueryOptions allow, string query, string unused)
[InlineData("top", "5", AllowedQueryOptions.Top)]
[InlineData("inlinecount", "none", AllowedQueryOptions.InlineCount)]
[InlineData("select", "Name", AllowedQueryOptions.Select)]
[InlineData("expand", "Contacts", AllowedQueryOptions.Expand)]
[InlineData("format", "json", AllowedQueryOptions.Format)]
[InlineData("skiptoken", "token", AllowedQueryOptions.SkipToken)]
public void Validate_DoesNotThrow_ForAllowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
{ {
// Arrange // Arrange
HttpRequestMessage message = new HttpRequestMessage( HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
HttpMethod.Get,
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
);
ODataQueryOptions option = new ODataQueryOptions(_context, message); ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings() ODataValidationSettings settings = new ODataValidationSettings()
{ {
AllowedQueryOptions = queryOption AllowedQueryOptions = allow,
}; };
// Act & Assert // Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings)); Assert.DoesNotThrow(() => _validator.Validate(option, settings));
} }
[Theory]
[PropertyData("SupportedQueryOptions")]
[PropertyData("UnsupportedQueryOptions")]
public void AllowedQueryOptions_ThrowIfNotAllowed(AllowedQueryOptions exclude, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~exclude,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("SupportedQueryOptions")]
[PropertyData("UnsupportedQueryOptions")]
public void AllowedQueryOptions_ThrowIfNoneAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.None,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("SupportedQueryOptions")]
public void SupportedQueryOptions_SucceedIfGroupAllowed(AllowedQueryOptions unused, string query, string unusedName)
{
// Arrange
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings));
}
[Theory]
[PropertyData("SupportedQueryOptions")]
public void SupportedQueryOptions_ThrowIfGroupNotAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("UnsupportedQueryOptions")]
public void UnsupportedQueryOptions_SucceedIfGroupAllowed(AllowedQueryOptions unused, string query, string unusedName)
{
// Arrange
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings));
}
[Theory]
[PropertyData("UnsupportedQueryOptions")]
public void UnsupportedQueryOptions_ThrowIfGroupNotAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Fact] [Fact]
public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull() public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull()
{ {

Просмотреть файл

@ -20,6 +20,11 @@ namespace System.Web.Http.OData.Query.Validators
return new ODataQueryContext(GetProductsModel(), typeof(Product)); return new ODataQueryContext(GetProductsModel(), typeof(Product));
} }
internal static ODataQueryContext CreateDerivedProductsContext()
{
return new ODataQueryContext(GetDerivedProductsModel(), typeof(Product));
}
private static IEdmModel GetCustomersModel() private static IEdmModel GetCustomersModel()
{ {
HttpConfiguration configuration = new HttpConfiguration(); HttpConfiguration configuration = new HttpConfiguration();
@ -31,12 +36,27 @@ namespace System.Web.Http.OData.Query.Validators
} }
private static IEdmModel GetProductsModel() private static IEdmModel GetProductsModel()
{
var builder = GetProductsBuilder();
return builder.GetEdmModel();
}
private static IEdmModel GetDerivedProductsModel()
{
var builder = GetProductsBuilder();
builder.EntitySet<Product>("Product");
builder.Entity<DerivedProduct>().DerivesFrom<Product>();
builder.Entity<DerivedCategory>().DerivesFrom<Category>();
return builder.GetEdmModel();
}
private static ODataConventionModelBuilder GetProductsBuilder()
{ {
HttpConfiguration configuration = new HttpConfiguration(); HttpConfiguration configuration = new HttpConfiguration();
configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product))); configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product)));
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration); ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration);
builder.EntitySet<Product>("Product"); builder.EntitySet<Product>("Product");
return builder.GetEdmModel(); return builder;
} }
} }
} }

Просмотреть файл

@ -103,6 +103,7 @@
<Compile Include="OData\EdmComplexCollectionObjectTest.cs" /> <Compile Include="OData\EdmComplexCollectionObjectTest.cs" />
<Compile Include="OData\EdmEntityCollectionObjectTest.cs" /> <Compile Include="OData\EdmEntityCollectionObjectTest.cs" />
<Compile Include="OData\EdmStructuredObjectTest.cs" /> <Compile Include="OData\EdmStructuredObjectTest.cs" />
<Compile Include="OData\EnableQueryTest.cs" />
<Compile Include="OData\FastPropertyAccessorTest.cs" /> <Compile Include="OData\FastPropertyAccessorTest.cs" />
<Compile Include="OData\Formatter\ClrTypeCacheTest.cs" /> <Compile Include="OData\Formatter\ClrTypeCacheTest.cs" />
<Compile Include="OData\Formatter\Deserialization\ODataDeserializerContextTest.cs" /> <Compile Include="OData\Formatter\Deserialization\ODataDeserializerContextTest.cs" />

Просмотреть файл

@ -0,0 +1,696 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Query;
using System.Web.OData.TestCommon;
using Microsoft.OData.Edm;
using Microsoft.TestCommon;
namespace System.Web.OData.Test
{
public class EnableQueryTests
{
// Other not allowed query options like $orderby, $top, etc.
public static TheoryDataSet<string, string> OtherQueryOptionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$orderby=Id", "OrderBy"},
{"?$top=5", "Top"},
{"?$skip=10", "Skip"},
{"?$count=true", "Count"},
{"?$select=Id", "Select"},
{"?$expand=Orders", "Expand"},
};
}
}
// Other unsupported query options
public static TheoryDataSet<string, string> OtherUnsupportedQueryOptionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$skiptoken=5", "SkipToken"},
};
}
}
public static TheoryDataSet<string, string> LogicalOperatorsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// And and or operators
{"?$filter=Adult or false", "'Or'"},
{"?$filter=true and Adult", "'And'"},
// Logical operators with simple property
{"?$filter=Id ne 5", "'NotEqual'"},
{"?$filter=Id gt 5", "'GreaterThan'"},
{"?$filter=Id ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Id lt 5", "'LessThan'"},
{"?$filter=Id le 5", "'LessThanOrEqual'"},
// Logical operators with property in a complex type property
{"?$filter=Address/ZipCode ne 5", "'NotEqual'"},
{"?$filter=Address/ZipCode gt 5", "'GreaterThan'"},
{"?$filter=Address/ZipCode ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Address/ZipCode lt 5", "'LessThan'"},
{"?$filter=Address/ZipCode le 5", "'LessThanOrEqual'"},
// Logical operators with property in a single valued navigation property
{"?$filter=Category/Id ne 5", "'NotEqual'"},
{"?$filter=Category/Id gt 5", "'GreaterThan'"},
{"?$filter=Category/Id ge 5", "'GreaterThanOrEqual'"},
{"?$filter=Category/Id lt 5", "'LessThan'"},
{"?$filter=Category/Id le 5", "'LessThanOrEqual'"},
// Logical operators with property in a derived type in a single valued navigation property
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel ne 5", "NotEqual'"},
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel gt 5", "GreaterThan'"},
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel ge 5", "GreaterThanOrEqual'"},
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel lt 5", "LessThan'"},
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel le 5", "LessThanOrEqual'"},
// not operator
{"?$filter=not Adult", "'Not'"},
};
}
}
public static TheoryDataSet<string, string> EqualsOperatorTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=Id eq 5", "Equal"},
{"?$filter=Address/ZipCode eq 5", "Equal"},
{"?$filter=Category/Id eq 5", "Equal"},
{"?$filter=Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel eq 5", "Equal"},
};
}
}
public static TheoryDataSet<string, string> ArithmeticOperatorsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Arithmetic operators with simple property
{"?$filter=1 eq (3 add Id)", "Add"},
{"?$filter=1 eq (3 sub Id)", "Subtract"},
{"?$filter=1 eq (1 mul Id)", "Multiply"},
{"?$filter=1 eq (Id div 1)", "Divide"},
{"?$filter=1 eq (Id mod 1)", "Modulo"},
// Arithmetic operators with property in a complex type property
{"?$filter=1 eq (3 add Address/ZipCode)", "Add"},
{"?$filter=1 eq (3 sub Address/ZipCode)", "Subtract"},
{"?$filter=1 eq (1 mul Address/ZipCode)", "Multiply"},
{"?$filter=1 eq (Address/ZipCode div 1)", "Divide"},
{"?$filter=1 eq (Address/ZipCode mod 1)", "Modulo"},
// Arithmetic operators with property in a single valued navigation property
{"?$filter=1 eq (3 add Category/Id)", "Add"},
{"?$filter=1 eq (3 sub Category/Id)", "Subtract"},
{"?$filter=1 eq (1 mul Category/Id)", "Multiply"},
{"?$filter=1 eq (Category/Id div 1)", "Divide"},
{"?$filter=1 eq (Category/Id mod 1)", "Modulo"},
// Arithmetic operators with property in a derived type in a single valued navigation property
{"?$filter=1 eq (3 add Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Add"},
{"?$filter=1 eq (3 sub Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Subtract"},
{"?$filter=1 eq (1 mul Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel)", "Multiply"},
{"?$filter=1 eq (Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel div 1)", "Divide"},
{"?$filter=1 eq (Category/System.Web.OData.Test.PremiumEnableQueryCategory/PremiumLevel mod 1)", "Modulo"},
};
}
}
public static TheoryDataSet<string, string> AnyAndAllFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Primitive collection property
{"?$filter=Points/any()", "any"},
{"?$filter=Points/any(p: p eq 1)", "any"},
{"?$filter=Points/all(p: p eq 1)", "all"},
// Complex type collection property
{"?$filter=Addresses/any()", "any"},
{"?$filter=Addresses/any(a: a/ZipCode eq 1)", "any"},
{"?$filter=Addresses/all(a: a/ZipCode eq 1)", "all"},
// Collection navigation property
{"?$filter=Orders/any()", "any"},
{"?$filter=Orders/any(o: o/Id eq 1)", "any"},
{"?$filter=Orders/all(o: o/Id eq 1)", "all"},
// Collection navigation property with casts
{"?$filter=Orders/any(o: o/System.Web.OData.Test.DiscountedEnableQueryOrder/Discount eq 1)", "any"},
{"?$filter=Orders/all(o: o/System.Web.OData.Test.DiscountedEnableQueryOrder/Discount eq 1)", "all"},
{"?$filter=Orders/System.Web.OData.Test.DiscountedEnableQueryOrder/any()", "any"},
{"?$filter=Orders/System.Web.OData.Test.DiscountedEnableQueryOrder/any(o: o/Discount eq 1)", "any"},
{"?$filter=Orders/System.Web.OData.Test.DiscountedEnableQueryOrder/all(o: o/Discount eq 1)", "all"},
};
}
}
public static TheoryDataSet<string, string> CastFunctionTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Entity type casts
{"?$filter=cast(Category,'System.Web.OData.Test.PremiumEnableQueryCategory') eq null", "cast"},
{"?$filter=cast(Id, Edm.Double) eq 2", "cast"},
{"?$filter=cast(Id, 'Edm.Double') eq 2", "cast"},
{"?$filter=cast('System.Web.OData.Test.PremiumEnableQueryCustomer') eq null", "cast"},
};
}
}
public static TheoryDataSet<string, string> IsOfFunctionTestData
{
get
{
return new TheoryDataSet<string, string>
{
// Entity type casts
{"?$filter=isof(Category,'System.Web.OData.Test.PremiumEnableQueryCategory')", "isof"},
{"?$filter=isof('System.Web.OData.Test.PremiumEnableQueryCustomer')", "isof"},
};
}
}
public static TheoryDataSet<string, string> StringFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=startswith(Name, 'Customer')", "startswith"},
{"?$filter=endswith(Name, 'Customer')", "endswith"},
{"?$filter=contains(Name, 'Customer')", "contains"},
{"?$filter=length(Name) eq 1", "length"},
{"?$filter=indexof(Name, 'Customer') eq 1", "indexof"},
{"?$filter=concat('Customer', Name) eq 'Customer'", "concat"},
{"?$filter=substring(Name, 3) eq 'Customer'", "substring"},
{"?$filter=substring(Name, 3, 3) eq 'Customer'", "substring"},
{"?$filter=tolower(Name) eq 'customer'", "tolower"},
{"?$filter=toupper(Name) eq 'CUSTOMER'", "toupper"},
{"?$filter=trim(Name) eq 'Customer'", "trim"},
};
}
}
public static TheoryDataSet<string, string> MathFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=round(Id) eq 1", "round"},
{"?$filter=floor(Id) eq 1", "floor"},
{"?$filter=ceiling(Id) eq 1", "ceiling"},
};
}
}
public static TheoryDataSet<string, string> SupportedDateTimeFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=year(AbsoluteBirthDate) eq 1987", "year"},
{"?$filter=month(AbsoluteBirthDate) eq 1987", "month"},
{"?$filter=day(AbsoluteBirthDate) eq 1987", "day"},
{"?$filter=hour(AbsoluteBirthDate) eq 1987", "hour"},
{"?$filter=minute(AbsoluteBirthDate) eq 1987", "minute"},
{"?$filter=second(AbsoluteBirthDate) eq 1987", "second"},
};
}
}
// These represent time functions that we validate but for which we don't support
// end to end.
public static TheoryDataSet<string, string> UnsupportedDateTimeFunctionsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$filter=years(Time) eq 1987", "years"},
{"?$filter=months(Time) eq 1987", "months"},
{"?$filter=days(Time) eq 1987", "days"},
{"?$filter=hours(Time) eq 1987", "hours"},
{"?$filter=minutes(Time) eq 1987", "minutes"},
{"?$filter=seconds(Time) eq 1987", "seconds"},
};
}
}
// Other limitations like MaxSkip, MaxTop, AllowedOrderByProperties, etc.
public static TheoryDataSet<string, string> NumericQueryLimitationsTestData
{
get
{
return new TheoryDataSet<string, string>
{
{"?$orderby=Name desc, Id asc", "$orderby"},
{"?$skip=20", "Skip"},
{"?$top=20", "Top"},
{"?$expand=Orders($expand=OrderLines)", "$expand"},
{"?$filter=Orders/any(o: o/OrderLines/all(ol: ol/Id gt 0))", "MaxAnyAllExpressionDepth"},
{"?$filter=Orders/any(o: o/Total gt 0) and Id eq 5", "MaxNodeCount"},
};
}
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
[PropertyData("OtherQueryOptionsTestData")]
[PropertyData("OtherUnsupportedQueryOptionsTestData")]
public void EnableQuery_Blocks_NotAllowedQueries(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OnlyFilterAndEqualsAllowedCustomers";
HttpServer server = CreateServer("OnlyFilterAndEqualsAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
public void EnableQuery_BlocksFilter_WhenNotAllowed(string queryString, string unused)
{
// Arrange
string url = "http://localhost/odata/FilterDisabledCustomers";
HttpServer server = CreateServer("FilterDisabledCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains("Filter", errorMessage);
}
[Theory]
[PropertyData("OtherUnsupportedQueryOptionsTestData")]
public void EnableQuery_ReturnsBadRequest_ForUnsupportedQueryOptions(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
// We check equals separately because we need to use it in the rest of the
// tests to produce valid filter expressions in other cases, so we need to
// enable it in those tests and this test only makes sure it covers the case
// when everything is disabled
[Theory]
[PropertyData("EqualsOperatorTestData")]
public void EnableQuery_BlocksEquals_WhenNotAllowed(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OnlyFilterAllowedCustomers";
HttpServer server = CreateServer("OnlyFilterAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("not allowed", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("LogicalOperatorsTestData")]
[PropertyData("ArithmeticOperatorsTestData")]
[PropertyData("EqualsOperatorTestData")]
[PropertyData("OtherQueryOptionsTestData")]
[PropertyData("StringFunctionsTestData")]
[PropertyData("MathFunctionsTestData")]
[PropertyData("SupportedDateTimeFunctionsTestData")]
[PropertyData("AnyAndAllFunctionsTestData")]
[PropertyData("CastFunctionTestData")]
public void EnableQuery_DoesNotBlockQueries_WhenEverythingIsAllowed(string queryString, string unused)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Theory]
[PropertyData("UnsupportedDateTimeFunctionsTestData")]
public void EnableQuery_ReturnsBadRequest_ForUnsupportedFunctions(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("unknown function", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("IsOfFunctionTestData")]
public void EnableQuery_ReturnsBadRequest_ForIsOf(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/EverythingAllowedCustomers";
HttpServer server = CreateServer("EverythingAllowedCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains("Unknown function", errorMessage);
Assert.Contains(expectedElement, errorMessage);
}
[Theory]
[PropertyData("NumericQueryLimitationsTestData")]
public void EnableQuery_BlocksQueries_WithOtherLimitations(string queryString, string expectedElement)
{
// Arrange
string url = "http://localhost/odata/OtherLimitationsCustomers";
HttpServer server = CreateServer("OtherLimitationsCustomers");
HttpClient client = new HttpClient(server);
// Act
HttpResponseMessage response = client.GetAsync(url + queryString).Result;
string errorMessage = response.Content.ReadAsStringAsync().Result;
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Contains(expectedElement, errorMessage);
}
// This controller limits any operation except for $filter and the eq operator
// in order to validate that all limitations work.
public class OnlyFilterAndEqualsAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.Equal,
AllowedQueryOptions = AllowedQueryOptions.Filter,
AllowedArithmeticOperators = AllowedArithmeticOperators.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OnlyFilterAndEqualsAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller exposes an action that limits everything except for
// filtering in order to verify that limiting the eq operator works.
// We didn't limit the eq operator in other queries as we need it to
// create valid filter queries using other limited elements and we
// want the query to fail because of limitations imposed on other
// elements rather than eq.
public class OnlyFilterAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(
AllowedFunctions = AllowedFunctions.None,
AllowedLogicalOperators = AllowedLogicalOperators.None,
AllowedQueryOptions = AllowedQueryOptions.Filter,
AllowedArithmeticOperators = AllowedArithmeticOperators.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OnlyFilterAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller disables all the query options ($filter amongst them)
public class FilterDisabledCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.None)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static FilterDisabledCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller doesn't limit anything specific to ensure that the
// requests succeed if they aren't limited.
public class EverythingAllowedCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static EverythingAllowedCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
// This controller exposes an action that has limitations on aspects
// other than AllowedFunctions, AllowedLogicalOperators, etc.
public class OtherLimitationsCustomersController : ODataController
{
private static readonly IQueryable<EnableQueryCustomer> _customers;
[EnableQuery(MaxNodeCount = 5,
MaxExpansionDepth = 1,
MaxAnyAllExpressionDepth = 1,
MaxSkip = 5,
MaxTop = 5,
MaxOrderByNodeCount = 1)]
public IQueryable<EnableQueryCustomer> Get()
{
return _customers;
}
static OtherLimitationsCustomersController()
{
_customers = CreateCustomers().AsQueryable();
}
}
private static HttpServer CreateServer(string customersEntitySet)
{
HttpConfiguration configuration = new HttpConfiguration();
// We need to do this to avoid controllers with incorrect attribute
// routing configuration in this assembly that cause an exception to
// be thrown at runtime. With this, we restrict the test to the following
// set of controllers.
configuration.Services.Replace(
typeof(IAssembliesResolver),
new TestAssemblyResolver(
typeof(OnlyFilterAllowedCustomersController),
typeof(OnlyFilterAndEqualsAllowedCustomersController),
typeof(FilterDisabledCustomersController),
typeof(EverythingAllowedCustomersController),
typeof(OtherLimitationsCustomersController)));
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<EnableQueryCustomer>(customersEntitySet);
builder.EntityType<PremiumEnableQueryCustomer>();
builder.EntitySet<EnableQueryCategory>("EnableQueryCategories");
builder.EntityType<PremiumEnableQueryCategory>();
builder.EntitySet<EnableQueryOrder>("EnableQueryOrders");
builder.EntityType<DiscountedEnableQueryOrder>();
builder.EntitySet<EnableQueryOrderLine>("EnableQueryOrderLines");
builder.ComplexType<EnableQueryAddress>();
IEdmModel model = builder.GetEdmModel();
configuration.MapODataServiceRoute("odata", "odata", model);
return new HttpServer(configuration);
}
// We need to create the data as we need the queries to succeed in one scenario.
private static IEnumerable<EnableQueryCustomer> CreateCustomers()
{
PremiumEnableQueryCustomer customer = new PremiumEnableQueryCustomer();
customer.Id = 1;
customer.Name = "Customer 1";
customer.Points = Enumerable.Range(1, 10).ToList();
customer.Address = new EnableQueryAddress { ZipCode = 1 };
customer.Addresses = Enumerable.Range(1, 10).Select(j => new EnableQueryAddress { ZipCode = j }).ToList();
customer.Category = new PremiumEnableQueryCategory
{
Id = 1,
PremiumLevel = 1,
};
customer.Orders = Enumerable.Range(1, 10).Select(j => new DiscountedEnableQueryOrder
{
Id = j,
Total = j,
Discount = j,
}).ToList<EnableQueryOrder>();
yield return customer;
}
public class EnableQueryCustomer
{
public int Id { get; set; }
public string Name { get; set; }
public EnableQueryCategory Category { get; set; }
public ICollection<EnableQueryOrder> Orders { get; set; }
public ICollection<int> Points { get; set; }
public ICollection<EnableQueryAddress> Addresses { get; set; }
public EnableQueryAddress Address { get; set; }
public DateTimeOffset AbsoluteBirthDate { get; set; }
public TimeSpan Time { get; set; }
public bool Adult { get; set; }
}
public class PremiumEnableQueryCustomer : EnableQueryCustomer
{
}
public class EnableQueryOrder
{
public int Id { get; set; }
public double Total { get; set; }
public ICollection<EnableQueryOrderLine> OrderLines { get; set; }
}
public class EnableQueryOrderLine
{
public int Id { get; set; }
}
public class DiscountedEnableQueryOrder : EnableQueryOrder
{
public double Discount { get; set; }
}
public class EnableQueryCategory
{
public int Id { get; set; }
}
public class PremiumEnableQueryCategory : EnableQueryCategory
{
public int PremiumLevel { get; set; }
}
public class EnableQueryAddress
{
public int ZipCode { get; set; }
}
}
}

Просмотреть файл

@ -26,18 +26,24 @@ namespace System.Web.OData.Query.Expressions
public bool? Discontinued { get; set; } public bool? Discontinued { get; set; }
public DateTimeOffset? DiscontinuedDate { get; set; } public DateTimeOffset? DiscontinuedDate { get; set; }
public DateTimeOffset NonNullableDiscontinuedDate { get; set; } public DateTimeOffset NonNullableDiscontinuedDate { get; set; }
[NotFilterable]
public DateTimeOffset NotFilterableDiscontinuedDate { get; set; }
public DateTimeOffset DiscontinuedOffset { get; set; } public DateTimeOffset DiscontinuedOffset { get; set; }
public TimeSpan DiscontinuedSince { get; set; } public TimeSpan DiscontinuedSince { get; set; }
public ushort? UnsignedReorderLevel { get; set; } public ushort? UnsignedReorderLevel { get; set; }
public SimpleEnum Ranking { get; set; }
public Category Category { get; set; } public Category Category { get; set; }
public Address SupplierAddress { get; set; } public Address SupplierAddress { get; set; }
public int[] AlternateIDs { get; set; } public int[] AlternateIDs { get; set; }
public Address[] AlternateAddresses { get; set; } public Address[] AlternateAddresses { get; set; }
[NotFilterable]
public Address[] NotFilterableAlternateAddresses { get; set; }
} }
public class Category public class Category

Просмотреть файл

@ -6,7 +6,6 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Web.Http;
using System.Web.Http.Dispatcher; using System.Web.Http.Dispatcher;
using System.Web.OData.Builder; using System.Web.OData.Builder;
using System.Xml.Linq; using System.Xml.Linq;
@ -425,7 +424,7 @@ namespace System.Web.OData.Query.Expressions
var filters = VerifyQueryDeserialization<DataTypes>(filter); var filters = VerifyQueryDeserialization<DataTypes>(filter);
var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value }); var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value });
Assert.Equal(result, expectedResult); Assert.Equal(expectedResult, result);
} }
// Issue: 477 // Issue: 477
@ -1559,6 +1558,7 @@ namespace System.Web.OData.Query.Expressions
[InlineData("cast('123',Microsoft.TestCommon.Types.SimpleEnum) ne null", "$it => (Convert(123) != null)")] [InlineData("cast('123',Microsoft.TestCommon.Types.SimpleEnum) ne null", "$it => (Convert(123) != null)")]
public void CastMethod_Succeeds(string filter, string expectedResult) public void CastMethod_Succeeds(string filter, string expectedResult)
{ {
// Arrange & Act & Assert
VerifyQueryDeserialization<DataTypes>( VerifyQueryDeserialization<DataTypes>(
filter, filter,
expectedResult, expectedResult,
@ -1566,23 +1566,113 @@ namespace System.Web.OData.Query.Expressions
} }
[Theory] [Theory]
[InlineData("cast(NoSuchProperty,Edm.Int32) ne null", "Could not find a property named 'NoSuchProperty' on type 'System.Web.OData.Query.Expressions.DataTypes'.")] [InlineData("cast(NoSuchProperty,Edm.Int32) ne null",
[InlineData("cast(null,Edm.Unknown) ne null", "The child type 'Edm.Unknown' in a cast was not an entity type. Casts can only be performed on entity types.")] "Could not find a property named 'NoSuchProperty' on type 'System.Web.OData.Query.Expressions.DataTypes'.")]
public void CastFails_UndefinedSourceOrTarget_Throws(string filter, string errorMessage) public void Cast_UndefinedSource_ThrowsODataException(string filter, string errorMessage)
{ {
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage); Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage);
} }
public static TheoryDataSet<string, string> CastToUnquotedUndefinedTarget
{
get
{
return new TheoryDataSet<string, string>
{
{ "cast(Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast(null,Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(null,Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast('2001-01-01T12:00:00.000',Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast('2001-01-01T12:00:00.000',Edm.Unknown) eq null", "Edm.Unknown" },
{ "cast(DateTimeProp,Edm.DateTime) eq null", "Edm.DateTime" },
{ "cast(DateTimeProp,Edm.Unknown) eq null", "Edm.Unknown" },
};
}
}
// Exception messages here and in CastQuotedUndefinedTarget_ThrowsODataException should be consistent.
// Worse, this message is incorrect -- casts can be performed on most types but _not_ entity types.
[Theory] [Theory]
[PropertyData("CastToUnquotedUndefinedTarget")]
public void CastToUnquotedUndefinedTarget_ThrowsODataException(string filter, string typeName)
{
// Arrange
var expectedMessage = string.Format(
"The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types.",
typeName);
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> CastToQuotedUndefinedTarget
{
get
{
return new TheoryDataSet<string>
{
{ "cast('Edm.DateTime') eq null" },
{ "cast('Edm.Unknown') eq null" },
{ "cast(null,'Edm.DateTime') eq null" },
{ "cast(null,'Edm.Unknown') eq null" },
{ "cast('2001-01-01T12:00:00.000','Edm.DateTime') eq null" },
{ "cast('','Edm.Unknown') eq null" },
{ "cast(DateTimeProp,'Edm.DateTime') eq null" },
{ "cast(IntProp,'Edm.Unknown') eq null" },
};
}
}
[Theory]
[PropertyData("CastToQuotedUndefinedTarget")]
public void CastToQuotedUndefinedTarget_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage = "Cast or IsOf Function must have a type in its arguments.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
[Theory]
[InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.FlagsEnum) ne null")]
[InlineData("cast(0,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(0,Microsoft.TestCommon.Types.FlagsEnum) ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum'0',Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.FlagsEnum'0',Microsoft.TestCommon.Types.FlagsEnum) ne null")]
[InlineData("cast(SimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")] [InlineData("cast(SimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(FlagsEnumProp,Microsoft.TestCommon.Types.FlagsEnum) ne null")] [InlineData("cast(FlagsEnumProp,Microsoft.TestCommon.Types.FlagsEnum) ne null")]
[InlineData("cast(NullableSimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")] [InlineData("cast(NullableSimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(IntProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")] [InlineData("cast(IntProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(DateTimeOffsetProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")] [InlineData("cast(DateTimeOffsetProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum'1',Edm.Int32) eq 1")]
[InlineData("cast(Microsoft.TestCommon.Types.FlagsEnum'1',Edm.Int32) eq 1")]
[InlineData("cast(SimpleEnumProp,Edm.Int32) eq 123")]
[InlineData("cast(FlagsEnumProp,Edm.Int32) eq 123")] [InlineData("cast(FlagsEnumProp,Edm.Int32) eq 123")]
[InlineData("cast(NullableSimpleEnumProp,Edm.Guid) ne null")] [InlineData("cast(NullableSimpleEnumProp,Edm.Guid) ne null")]
public void CastFails_UnsupportedSourceOrTargetForEnumCast_Throws(string filter)
[InlineData("cast('Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast('Microsoft.TestCommon.Types.FlagsEnum') ne null")]
[InlineData("cast(0,'Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(0,'Microsoft.TestCommon.Types.FlagsEnum') ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum'0','Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.FlagsEnum'0','Microsoft.TestCommon.Types.FlagsEnum') ne null")]
[InlineData("cast(SimpleEnumProp,'Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(FlagsEnumProp,'Microsoft.TestCommon.Types.FlagsEnum') ne null")]
[InlineData("cast(NullableSimpleEnumProp,'Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(IntProp,'Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(DateTimeOffsetProp,'Microsoft.TestCommon.Types.SimpleEnum') ne null")]
[InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum'1','Edm.Int32') eq 1")]
[InlineData("cast(Microsoft.TestCommon.Types.FlagsEnum'1','Edm.Int32') eq 1")]
[InlineData("cast(SimpleEnumProp,'Edm.Int32') eq 123")]
[InlineData("cast(FlagsEnumProp,'Edm.Int32') eq 123")]
[InlineData("cast(NullableSimpleEnumProp,'Edm.Guid') ne null")]
public void Cast_UnsupportedSourceOrTargetForEnumCast_Throws(string filter)
{ {
// Arrange & Act & Assert
// TODO : 1824 Should not throw exception for invalid enum cast in query option. // TODO : 1824 Should not throw exception for invalid enum cast in query option.
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), "Enumeration type value can only be casted to or from string."); Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), "Enumeration type value can only be casted to or from string.");
} }
@ -1601,18 +1691,23 @@ namespace System.Web.OData.Query.Expressions
[InlineData("cast(ComplexProp,Edm.String) eq null")] [InlineData("cast(ComplexProp,Edm.String) eq null")]
[InlineData("cast(StringProp,Microsoft.TestCommon.Types.SimpleEnum) eq null")] [InlineData("cast(StringProp,Microsoft.TestCommon.Types.SimpleEnum) eq null")]
[InlineData("cast(StringProp,Microsoft.TestCommon.Types.FlagsEnum) eq null")] [InlineData("cast(StringProp,Microsoft.TestCommon.Types.FlagsEnum) eq null")]
public void CastFails_UnsupportedTarget_ReturnsNull(string filter) public void Cast_UnsupportedTarget_ReturnsNull(string filter)
{ {
// Arrange & Act & Assert
VerifyQueryDeserialization<DataTypes>(filter, "$it => (null == null)"); VerifyQueryDeserialization<DataTypes>(filter, "$it => (null == null)");
} }
// See OtherFunctions_SomeTwoParameterCasts_ThrowODataException and OtherFunctions_SomeSingleParameterCasts_ThrowODataException
// in FilterQueryValidatorTest. ODL's ODataQueryOptionParser and FunctionCallBinder call the code throwing these exceptions.
[Theory] [Theory]
[InlineData("cast(null,System.Web.OData.Query.Expressions.Address) ne null", [InlineData("cast(null,System.Web.OData.Query.Expressions.Address) ne null",
"Encountered invalid type cast. 'System.Web.OData.Query.Expressions.Address' is not assignable from 'System.Web.OData.Query.Expressions.DataTypes'.")] "Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.Address' is not assignable from 'System.Web.OData.Query.Expressions.DataTypes'.")]
[InlineData("cast(null,System.Web.OData.Query.Expressions.DataTypes) ne null", [InlineData("cast(null,System.Web.OData.Query.Expressions.DataTypes) ne null",
"Cast or IsOf Function must have a type in its arguments.")] "Cast or IsOf Function must have a type in its arguments.")]
public void CastFails_NonPrimitiveTarget_Throws(string filter, string expectErrorMessage) public void Cast_NonPrimitiveTarget_ThrowsODataException(string filter, string expectErrorMessage)
{ {
// Arrange & Act & Assert
// TODO : 1827 Should not throw when the target type of cast is not primitive or enumeration type. // TODO : 1827 Should not throw when the target type of cast is not primitive or enumeration type.
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectErrorMessage); Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectErrorMessage);
} }
@ -1665,6 +1760,600 @@ namespace System.Web.OData.Query.Expressions
Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", ((ConstantNode)castNode.Parameters.Last()).Value); Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", ((ConstantNode)castNode.Parameters.Last()).Value);
} }
public static TheoryDataSet<string> CastToQuotedPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "cast('Edm.Binary') eq null" },
{ "cast('Edm.Boolean') eq null" },
{ "cast('Edm.Byte') eq null" },
{ "cast('Edm.DateTimeOffset') eq null" },
{ "cast('Edm.Decimal') eq null" },
{ "cast('Edm.Double') eq null" },
{ "cast('Edm.Duration') eq null" },
{ "cast('Edm.Guid') eq null" },
{ "cast('Edm.Int16') eq null" },
{ "cast('Edm.Int32') eq null" },
{ "cast('Edm.Int64') eq null" },
{ "cast('Edm.SByte') eq null" },
{ "cast('Edm.Single') eq null" },
{ "cast('Edm.String') eq null" },
{ "cast(null,'Edm.Binary') eq null" },
{ "cast(null,'Edm.Boolean') eq null" },
{ "cast(null,'Edm.Byte') eq null" },
{ "cast(null,'Edm.DateTimeOffset') eq null" },
{ "cast(null,'Edm.Decimal') eq null" },
{ "cast(null,'Edm.Double') eq null" },
{ "cast(null,'Edm.Duration') eq null" },
{ "cast(null,'Edm.Guid') eq null" },
{ "cast(null,'Edm.Int16') eq null" },
{ "cast(null,'Edm.Int32') eq null" },
{ "cast(null,'Edm.Int64') eq null" },
{ "cast(null,'Edm.SByte') eq null" },
{ "cast(null,'Edm.Single') eq null" },
{ "cast(null,'Edm.String') eq null" },
{ "cast(binary'T0RhdGE=','Edm.Binary') eq binary'T0RhdGE='" },
{ "cast(false,'Edm.Boolean') eq false" },
{ "cast(23,'Edm.Byte') eq 23" },
{ "cast(2001-01-01T12:00:00.000+08:00,'Edm.DateTimeOffset') eq 2001-01-01T12:00:00.000+08:00" },
{ "cast(23,'Edm.Decimal') eq 23" },
{ "cast(23,'Edm.Double') eq 23" },
{ "cast(duration'PT12H','Edm.Duration') eq duration'PT12H'" },
{ "cast(00000000-0000-0000-0000-000000000000,'Edm.Guid') eq 00000000-0000-0000-0000-000000000000" },
{ "cast(23,'Edm.Int16') eq 23" },
{ "cast(23,'Edm.Int32') eq 23" },
{ "cast(23,'Edm.Int64') eq 23" },
{ "cast(23,'Edm.SByte') eq 23" },
{ "cast(23,'Edm.Single') eq 23" },
{ "cast('hello','Edm.String') eq 'hello'" },
{ "cast(ByteArrayProp,'Edm.Binary') eq null" },
{ "cast(BoolProp,'Edm.Boolean') eq true" },
{ "cast(DateTimeOffsetProp,'Edm.DateTimeOffset') eq 2001-01-01T12:00:00.000+08:00" },
{ "cast(DecimalProp,'Edm.Decimal') eq 23" },
{ "cast(DoubleProp,'Edm.Double') eq 23" },
{ "cast(TimeSpanProp,'Edm.Duration') eq duration'PT23H'" },
{ "cast(GuidProp,'Edm.Guid') eq 0EFDAECF-A9F0-42F3-A384-1295917AF95E" },
{ "cast(NullableShortProp,'Edm.Int16') eq 23" },
{ "cast(IntProp,'Edm.Int32') eq 23" },
{ "cast(LongProp,'Edm.Int64') eq 23" },
{ "cast(FloatProp,'Edm.Single') eq 23" },
{ "cast(StringProp,'Edm.String') eq 'hello'" },
};
}
}
[Theory]
[PropertyData("CastToQuotedPrimitiveType")]
public void CastToQuotedPrimitiveType_Succeeds(string filter)
{
// Arrange
var model = new DataTypes
{
BoolProp = true,
DateTimeOffsetProp = DateTimeOffset.Parse("2001-01-01T12:00:00.000+08:00"),
DecimalProp = 23,
DoubleProp = 23,
GuidProp = Guid.Parse("0EFDAECF-A9F0-42F3-A384-1295917AF95E"),
NullableShortProp = 23,
IntProp = 23,
LongProp = 23,
FloatProp = 23,
StringProp = "hello",
TimeSpanProp = TimeSpan.FromHours(23),
};
// Act & Assert
var filters = VerifyQueryDeserialization<DataTypes>(
filter,
expectedResult: NotTesting,
expectedResultWithNullPropagation: NotTesting);
RunFilters(filters, model, expectedValue: new { WithNullPropagation = true, WithoutNullPropagation = true });
}
public static TheoryDataSet<string> CastToUnquotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "cast(System.Web.OData.Query.Expressions.Address) eq null" },
{ "cast(null, System.Web.OData.Query.Expressions.Address) eq null" },
{ "cast('', System.Web.OData.Query.Expressions.Address) eq null" },
{ "cast(SupplierAddress, System.Web.OData.Query.Expressions.Address) eq null" },
};
}
}
[Theory]
[PropertyData("CastToUnquotedComplexType")]
public void CastToUnquotedComplexType_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage =
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.Address' is not assignable from 'System.Web.OData.Query.Expressions.Product'.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string> CastToQuotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "cast('System.Web.OData.Query.Expressions.Address') eq null" },
{ "cast(null, 'System.Web.OData.Query.Expressions.Address') eq null" },
{ "cast('', 'System.Web.OData.Query.Expressions.Address') eq null" },
{ "cast(SupplierAddress, 'System.Web.OData.Query.Expressions.Address') ne null" },
};
}
}
[Theory]
[PropertyData("CastToQuotedComplexType")]
public void CastToQuotedComplexType_Succeeds(string filter)
{
// Arrange
var model = new Product
{
SupplierAddress = new Address { City = "Redmond", },
};
// Act & Assert
var filters = VerifyQueryDeserialization<Product>(
filter,
expectedResult: NotTesting,
expectedResultWithNullPropagation: NotTesting);
RunFilters(filters, model, expectedValue: new { WithNullPropagation = true, WithoutNullPropagation = true });
}
public static TheoryDataSet<string, string> CastToUnquotedEntityType
{
get
{
return new TheoryDataSet<string, string>
{
{
"cast(System.Web.OData.Query.Expressions.DerivedProduct)/DerivedProductName eq null",
"Cast or IsOf Function must have a type in its arguments."
},
{
"cast(null, System.Web.OData.Query.Expressions.DerivedCategory)/DerivedCategoryName eq null",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
{
"cast(Category, System.Web.OData.Query.Expressions.DerivedCategory)/DerivedCategoryName eq null",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
};
}
}
[Theory]
[PropertyData("CastToUnquotedEntityType")]
public void CastToUnquotedEntityType_ThrowsODataException(string filter, string expectedMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
// Demonstrates a bug in FilterBinder.
[Theory]
[InlineData("cast('System.Web.OData.Query.Expressions.DerivedProduct')/DerivedProductName eq null", "DerivedProductName")]
[InlineData("cast(Category,'System.Web.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null", "DerivedCategoryName")]
[InlineData("cast(Category, 'System.Web.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null", "DerivedCategoryName")]
public void CastToQuotedEntityType_ThrowsArgumentException(string filter, string propertyName)
{
// Arrange
var expectedMessage = string.Format(
"Instance property '{0}' is not defined for type '{1}'",
propertyName,
typeof(object).FullName);
// Act & Assert
Assert.Throws<ArgumentException>(() => Bind<Product>(filter), expectedMessage);
}
[Theory]
[InlineData("cast(null,'System.Web.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
[InlineData("cast(null, 'System.Web.OData.Query.Expressions.DerivedCategory')/DerivedCategoryName eq null")]
public void CastNullToQuotedEntityType_ThrowsArgumentException(string filter)
{
// Arrange
var expectedMessage =
"An instance of SingleValueFunctionCallNode can only be created with a primitive, complex or enum type. " +
"For functions returning a single entity, use SingleEntityFunctionCallNode instead.";
// Act & Assert
Assert.Throws<ArgumentException>(() => Bind<Product>(filter), expectedMessage);
}
#endregion
#region 'isof' in query option
[Theory]
[InlineData("isof(NoSuchProperty,Edm.Int32)",
"Could not find a property named 'NoSuchProperty' on type 'System.Web.OData.Query.Expressions.DataTypes'.")]
public void IsOfUndefinedSource_ThrowsODataException(string filter, string errorMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage);
}
public static TheoryDataSet<string, string> IsOfUndefinedTarget
{
get
{
return new TheoryDataSet<string, string>
{
{ "isof(Edm.DateTime)", "Edm.DateTime" },
{ "isof(Edm.Unknown)", "Edm.Unknown" },
{ "isof(null,Edm.DateTime)", "Edm.DateTime" },
{ "isof(null,Edm.Unknown)", "Edm.Unknown" },
{ "isof('2001-01-01T12:00:00.000',Edm.DateTime)", "Edm.DateTime" },
{ "isof('',Edm.Unknown)", "Edm.Unknown" },
{ "isof(DateTimeProp,Edm.DateTime)", "Edm.DateTime" },
{ "isof(IntProp,Edm.Unknown)", "Edm.Unknown" },
};
}
}
// Exception messages here and in IsOfQuotedUndefinedTarget_ThrowsODataException should be consistent.
// Worse, this message is incorrect -- casts can be performed on most types but _not_ entity types and
// isof can't be performed.
[Theory]
[PropertyData("IsOfUndefinedTarget")]
public void IsOfUndefinedTarget_ThrowsODataException(string filter, string typeName)
{
// Arrange
var expectedMessage = string.Format(
"The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types.",
typeName);
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfQuotedUndefinedTarget
{
get
{
return new TheoryDataSet<string>
{
{ "isof('Edm.DateTime')" },
{ "isof('Edm.Unknown')" },
{ "isof(null,'Edm.DateTime')" },
{ "isof(null,'Edm.Unknown')" },
{ "isof('2001-01-01T12:00:00.000','Edm.DateTime')" },
{ "isof('','Edm.Unknown')" },
{ "isof(DateTimeProp,'Edm.DateTime')" },
{ "isof(IntProp,'Edm.Unknown')" },
};
}
}
[Theory]
[PropertyData("IsOfQuotedUndefinedTarget")]
public void IsOfQuotedUndefinedTarget_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage = "Cast or IsOf Function must have a type in its arguments.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "isof(Edm.Binary)" },
{ "isof(Edm.Boolean)" },
{ "isof(Edm.Byte)" },
{ "isof(Edm.DateTimeOffset)" },
{ "isof(Edm.Decimal)" },
{ "isof(Edm.Double)" },
{ "isof(Edm.Duration)" },
{ "isof(Edm.Guid)" },
{ "isof(Edm.Int16)" },
{ "isof(Edm.Int32)" },
{ "isof(Edm.Int64)" },
{ "isof(Edm.SByte)" },
{ "isof(Edm.Single)" },
{ "isof(Edm.Stream)" },
{ "isof(Edm.String)" },
{ "isof(Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof(null,Edm.Binary)" },
{ "isof(null,Edm.Boolean)" },
{ "isof(null,Edm.Byte)" },
{ "isof(null,Edm.DateTimeOffset)" },
{ "isof(null,Edm.Decimal)" },
{ "isof(null,Edm.Double)" },
{ "isof(null,Edm.Duration)" },
{ "isof(null,Edm.Guid)" },
{ "isof(null,Edm.Int16)" },
{ "isof(null,Edm.Int32)" },
{ "isof(null,Edm.Int64)" },
{ "isof(null,Edm.SByte)" },
{ "isof(null,Edm.Single)" },
{ "isof(null,Edm.Stream)" },
{ "isof(null,Edm.String)" },
{ "isof(null,Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(null,Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof(binary'T0RhdGE=',Edm.Binary)" },
{ "isof(false,Edm.Boolean)" },
{ "isof(23,Edm.Byte)" },
{ "isof(2001-01-01T12:00:00.000+08:00,Edm.DateTimeOffset)" },
{ "isof(23,Edm.Decimal)" },
{ "isof(23,Edm.Double)" },
{ "isof(duration'PT12H',Edm.Duration)" },
{ "isof(00000000-0000-0000-0000-000000000000,Edm.Guid)" },
{ "isof(23,Edm.Int16)" },
{ "isof(23,Edm.Int32)" },
{ "isof(23,Edm.Int64)" },
{ "isof(23,Edm.SByte)" },
{ "isof(23,Edm.Single)" },
{ "isof('hello',Edm.Stream)" },
{ "isof('hello',Edm.String)" },
{ "isof(0,Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(Microsoft.TestCommon.Types.SimpleEnum'0',Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(0,Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof(Microsoft.TestCommon.Types.FlagsEnum'0',Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof('OData',Edm.Binary)" },
{ "isof('false',Edm.Boolean)" },
{ "isof('23',Edm.Byte)" },
{ "isof('2001-01-01T12:00:00.000+08:00',Edm.DateTimeOffset)" },
{ "isof('23',Edm.Decimal)" },
{ "isof('23',Edm.Double)" },
{ "isof('PT12H',Edm.Duration)" },
{ "isof('00000000-0000-0000-0000-000000000000',Edm.Guid)" },
{ "isof('23',Edm.Int16)" },
{ "isof('23',Edm.Int32)" },
{ "isof('23',Edm.Int64)" },
{ "isof('23',Edm.SByte)" },
{ "isof('23',Edm.Single)" },
{ "isof(23,Edm.String)" },
{ "isof('0',Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof('0',Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(ByteArrayProp,Edm.Binary)" },
{ "isof(BoolProp,'Edm.Boolean')" },
{ "isof(DateTimeOffsetProp,Edm.DateTimeOffset)" },
{ "isof(DecimalProp,Edm.Decimal)" },
{ "isof(DoubleProp,Edm.Double)" },
{ "isof(TimeSpanProp,'Edm.Duration')" },
{ "isof(GuidProp,Edm.Guid)" },
{ "isof(NullableShortProp,Edm.Int16)" },
{ "isof(IntProp,Edm.Int32)" },
{ "isof(LongProp,Edm.Int64)" },
{ "isof(FloatProp,Edm.Single)" },
{ "isof(StringProp,Edm.String)" },
{ "isof(IntProp,Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof(FlagsEnumProp,Microsoft.TestCommon.Types.FlagsEnum)" },
{ "isof(SimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum)" },
{ "isof('Edm.Binary')" },
{ "isof('Edm.Boolean')" },
{ "isof('Edm.Byte')" },
{ "isof('Edm.DateTimeOffset')" },
{ "isof('Edm.Decimal')" },
{ "isof('Edm.Double')" },
{ "isof('Edm.Duration')" },
{ "isof('Edm.Guid')" },
{ "isof('Edm.Int16')" },
{ "isof('Edm.Int32')" },
{ "isof('Edm.Int64')" },
{ "isof('Edm.SByte')" },
{ "isof('Edm.Single')" },
{ "isof('Edm.Stream')" },
{ "isof('Edm.String')" },
{ "isof('Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof('Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof(null,'Edm.Binary')" },
{ "isof(null,'Edm.Boolean')" },
{ "isof(null,'Edm.Byte')" },
{ "isof(null,'Edm.DateTimeOffset')" },
{ "isof(null,'Edm.Decimal')" },
{ "isof(null,'Edm.Double')" },
{ "isof(null,'Edm.Duration')" },
{ "isof(null,'Edm.Guid')" },
{ "isof(null,'Edm.Int16')" },
{ "isof(null,'Edm.Int32')" },
{ "isof(null,'Edm.Int64')" },
{ "isof(null,'Edm.SByte')" },
{ "isof(null,'Edm.Single')" },
{ "isof(null,'Edm.Stream')" },
{ "isof(null,'Edm.String')" },
{ "isof(null,'Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof(null,'Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof(binary'T0RhdGE=','Edm.Binary')" },
{ "isof(false,'Edm.Boolean')" },
{ "isof(23,'Edm.Byte')" },
{ "isof(2001-01-01T12:00:00.000+08:00,'Edm.DateTimeOffset')" },
{ "isof(23,'Edm.Decimal')" },
{ "isof(23,'Edm.Double')" },
{ "isof(duration'PT12H','Edm.Duration')" },
{ "isof(00000000-0000-0000-0000-000000000000,'Edm.Guid')" },
{ "isof(23,'Edm.Int16')" },
{ "isof(23,'Edm.Int32')" },
{ "isof(23,'Edm.Int64')" },
{ "isof(23,'Edm.SByte')" },
{ "isof(23,'Edm.Single')" },
{ "isof('hello','Edm.Stream')" },
{ "isof('hello','Edm.String')" },
{ "isof(0,'Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof(Microsoft.TestCommon.Types.FlagsEnum'0','Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof(0,'Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof(Microsoft.TestCommon.Types.SimpleEnum'0','Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof('OData','Edm.Binary')" },
{ "isof('false','Edm.Boolean')" },
{ "isof('23','Edm.Byte')" },
{ "isof('2001-01-01T12:00:00.000+08:00','Edm.DateTimeOffset')" },
{ "isof('23','Edm.Decimal')" },
{ "isof('23','Edm.Double')" },
{ "isof('PT12H','Edm.Duration')" },
{ "isof('00000000-0000-0000-0000-000000000000','Edm.Guid')" },
{ "isof('23','Edm.Int16')" },
{ "isof('23','Edm.Int32')" },
{ "isof('23','Edm.Int64')" },
{ "isof('23','Edm.SByte')" },
{ "isof('23','Edm.Single')" },
{ "isof(23,'Edm.String')" },
{ "isof('0','Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof('0','Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof(ByteArrayProp,'Edm.Binary')" },
{ "isof(BoolProp,'Edm.Boolean')" },
{ "isof(DateTimeOffsetProp,'Edm.DateTimeOffset')" },
{ "isof(DecimalProp,'Edm.Decimal')" },
{ "isof(DoubleProp,'Edm.Double')" },
{ "isof(TimeSpanProp,'Edm.Duration')" },
{ "isof(GuidProp,'Edm.Guid')" },
{ "isof(NullableShortProp,'Edm.Int16')" },
{ "isof(IntProp,'Edm.Int32')" },
{ "isof(LongProp,'Edm.Int64')" },
{ "isof(FloatProp,'Edm.Single')" },
{ "isof(StringProp,'Edm.String')" },
{ "isof(IntProp,'Microsoft.TestCommon.Types.SimpleEnum')" },
{ "isof(FlagsEnumProp,'Microsoft.TestCommon.Types.FlagsEnum')" },
{ "isof(SimpleEnumProp,'Microsoft.TestCommon.Types.SimpleEnum')" },
};
}
}
// Demonstrates a missing feature in FilterBinder.
[Theory]
[PropertyData("IsOfPrimitiveType")]
public void IsOfPrimitiveType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'isof'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<DataTypes>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfUnquotedComplexType
{
get
{
return new TheoryDataSet<string>
{
{ "isof(System.Web.OData.Query.Expressions.Address)" },
{ "isof(null,System.Web.OData.Query.Expressions.Address)" },
{ "isof(null, System.Web.OData.Query.Expressions.Address)" },
{ "isof(SupplierAddress,System.Web.OData.Query.Expressions.Address)" },
{ "isof(SupplierAddress, System.Web.OData.Query.Expressions.Address)" },
};
}
}
[Theory]
[PropertyData("IsOfUnquotedComplexType")]
public void IsOfUnquotedComplexType_ThrowsODataException(string filter)
{
// Arrange
var expectedMessage =
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.Address' is not assignable from 'System.Web.OData.Query.Expressions.Product'.";
// Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string, string> IsOfUnquotedEntityType
{
get
{
return new TheoryDataSet<string, string>
{
{
"isof(System.Web.OData.Query.Expressions.DerivedProduct)",
"Cast or IsOf Function must have a type in its arguments."
},
{
"isof(null,System.Web.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
{
"isof(null, System.Web.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
{
"isof(Category,System.Web.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
{
"isof(Category, System.Web.OData.Query.Expressions.DerivedCategory)",
"Encountered invalid type cast. " +
"'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'."
},
};
}
}
[Theory]
[PropertyData("IsOfUnquotedEntityType")]
public void IsOfUnquotedEntityType_ThrowsODataException(string filter, string expectedMessage)
{
// Arrange & Act & Assert
Assert.Throws<ODataException>(() => Bind<Product>(filter), expectedMessage);
}
public static TheoryDataSet<string> IsOfQuotedNonPrimitiveType
{
get
{
return new TheoryDataSet<string>
{
{ "isof('System.Web.OData.Query.Expressions.Address')" },
{ "isof('System.Web.OData.Query.Expressions.DerivedProduct')" },
{ "isof(null,'System.Web.OData.Query.Expressions.Address')" },
{ "isof(null, 'System.Web.OData.Query.Expressions.Address')" },
{ "isof(null,'System.Web.OData.Query.Expressions.DerivedCategory')" },
{ "isof(null, 'System.Web.OData.Query.Expressions.DerivedCategory')" },
{ "isof(SupplierAddress,'System.Web.OData.Query.Expressions.Address')" },
{ "isof(SupplierAddress, 'System.Web.OData.Query.Expressions.Address')" },
{ "isof(Category,'System.Web.OData.Query.Expressions.DerivedCategory')" },
{ "isof(Category, 'System.Web.OData.Query.Expressions.DerivedCategory')" },
};
}
}
// Demonstrates a missing feature in FilterBinder.
[Theory]
[PropertyData("IsOfQuotedNonPrimitiveType")]
public void IsOfQuotedNonPrimitiveType_ThrowsNotImplemented(string filter)
{
// Arrange
var expectedMessage = "Unknown function 'isof'.";
// Act & Assert
Assert.Throws<NotImplementedException>(() => Bind<Product>(filter), expectedMessage);
}
#endregion #endregion
#region parameter alias for filter query option #region parameter alias for filter query option
@ -1979,7 +2668,7 @@ namespace System.Web.OData.Query.Expressions
} }
else else
{ {
Assert.Equal(RunFilter(filterWithNullPropagation, product), expectedValue.WithNullPropagation); Assert.Equal(expectedValue.WithNullPropagation, RunFilter(filterWithNullPropagation, product));
} }
var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>; var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>;
@ -1989,7 +2678,7 @@ namespace System.Web.OData.Query.Expressions
} }
else else
{ {
Assert.Equal(RunFilter(filterWithoutNullPropagation, product), expectedValue.WithoutNullPropagation); Assert.Equal(expectedValue.WithoutNullPropagation, RunFilter(filterWithoutNullPropagation, product));
} }
} }
@ -2068,6 +2757,12 @@ namespace System.Web.OData.Query.Expressions
{ {
ODataModelBuilder model = new ODataConventionModelBuilder(); ODataModelBuilder model = new ODataConventionModelBuilder();
model.EntitySet<T>("Products"); model.EntitySet<T>("Products");
if (key == typeof(Product))
{
model.EntityType<DerivedProduct>().DerivesFrom<Product>();
model.EntityType<DerivedCategory>().DerivesFrom<Category>();
}
value = _modelCache[key] = model.GetEdmModel(); value = _modelCache[key] = model.GetEdmModel();
} }
return value; return value;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,5 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using Microsoft.OData.Core; using Microsoft.OData.Core;
using Microsoft.TestCommon; using Microsoft.TestCommon;
@ -18,6 +20,35 @@ namespace System.Web.OData.Query.Validators
_context = ValidationTestHelper.CreateCustomerContext(); _context = ValidationTestHelper.CreateCustomerContext();
} }
public static TheoryDataSet<AllowedQueryOptions, string, string> SupportedQueryOptions
{
get
{
return new TheoryDataSet<AllowedQueryOptions, string, string>
{
{ AllowedQueryOptions.Count, "$count=true", "Count" },
{ AllowedQueryOptions.Expand, "$expand=Contacts", "Expand" },
{ AllowedQueryOptions.Filter, "$filter=Name eq 'Name'", "Filter" },
{ AllowedQueryOptions.Format, "$format=json", "Format" },
{ AllowedQueryOptions.OrderBy, "$orderby=Name", "OrderBy" },
{ AllowedQueryOptions.Select, "$select=Name", "Select" },
{ AllowedQueryOptions.Skip, "$skip=5", "Skip" },
{ AllowedQueryOptions.Top, "$top=10", "Top" },
};
}
}
public static TheoryDataSet<AllowedQueryOptions, string, string> UnsupportedQueryOptions
{
get
{
return new TheoryDataSet<AllowedQueryOptions, string, string>
{
{ AllowedQueryOptions.SkipToken, "$skiptoken=__skip__", "SkipToken" },
};
}
}
[Fact] [Fact]
public void ValidateThrowsOnNullOption() public void ValidateThrowsOnNullOption()
{ {
@ -32,64 +63,171 @@ namespace System.Web.OData.Query.Validators
_validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null)); _validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null));
} }
[Theory] [Fact]
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)] public void QueryOptionDataSets_CoverAllValues()
[InlineData("orderby", "Name", AllowedQueryOptions.OrderBy)]
[InlineData("skip", "5", AllowedQueryOptions.Skip)]
[InlineData("top", "5", AllowedQueryOptions.Top)]
[InlineData("count", "false", AllowedQueryOptions.Count)]
[InlineData("select", "Name", AllowedQueryOptions.Select)]
[InlineData("expand", "Contacts", AllowedQueryOptions.Expand)]
[InlineData("format", "json", AllowedQueryOptions.Format)]
[InlineData("skiptoken", "token", AllowedQueryOptions.SkipToken)]
public void Validate_Throws_ForDisallowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
{ {
// Arrange // Arrange
HttpRequestMessage message = new HttpRequestMessage( // Get all values in the AllowedQueryOptions enum.
HttpMethod.Get, var values = new HashSet<AllowedQueryOptions>(
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue) Enum.GetValues(typeof(AllowedQueryOptions)).Cast<AllowedQueryOptions>());
);
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~queryOption
};
// Act & Assert var groupValues = new[]
var exception = Assert.Throws<ODataException>(() => _validator.Validate(option, settings)); {
Assert.Equal( AllowedQueryOptions.All,
"Query option '" + queryOptionName + "' is not allowed. To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.", AllowedQueryOptions.None,
exception.Message, AllowedQueryOptions.Supported,
StringComparer.OrdinalIgnoreCase); };
var dataSets = SupportedQueryOptions.Concat(UnsupportedQueryOptions);
// Act
// Remove the group items.
foreach (var allowed in groupValues)
{
values.Remove(allowed);
}
// Remove the individual items.
foreach (var allowed in dataSets.Select(item => (AllowedQueryOptions)(item[0])))
{
values.Remove(allowed);
}
// Assert
// Should have nothing left.
Assert.Empty(values);
} }
[Theory] [Theory]
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)] [PropertyData("SupportedQueryOptions")]
[InlineData("orderby", "Name", AllowedQueryOptions.OrderBy)] [PropertyData("UnsupportedQueryOptions")]
[InlineData("skip", "5", AllowedQueryOptions.Skip)] public void AllowedQueryOptions_SucceedIfAllowed(AllowedQueryOptions allow, string query, string unused)
[InlineData("top", "5", AllowedQueryOptions.Top)]
[InlineData("count", "false", AllowedQueryOptions.Count)]
[InlineData("select", "Name", AllowedQueryOptions.Select)]
[InlineData("expand", "Contacts", AllowedQueryOptions.Expand)]
[InlineData("format", "json", AllowedQueryOptions.Format)]
[InlineData("skiptoken", "token", AllowedQueryOptions.SkipToken)]
public void Validate_DoesNotThrow_ForAllowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
{ {
// Arrange // Arrange
HttpRequestMessage message = new HttpRequestMessage( HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
HttpMethod.Get,
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
);
ODataQueryOptions option = new ODataQueryOptions(_context, message); ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings() ODataValidationSettings settings = new ODataValidationSettings()
{ {
AllowedQueryOptions = queryOption AllowedQueryOptions = allow,
}; };
// Act & Assert // Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings)); Assert.DoesNotThrow(() => _validator.Validate(option, settings));
} }
[Theory]
[PropertyData("SupportedQueryOptions")]
[PropertyData("UnsupportedQueryOptions")]
public void AllowedQueryOptions_ThrowIfNotAllowed(AllowedQueryOptions exclude, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~exclude,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("SupportedQueryOptions")]
[PropertyData("UnsupportedQueryOptions")]
public void AllowedQueryOptions_ThrowIfNoneAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.None,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("SupportedQueryOptions")]
public void SupportedQueryOptions_SucceedIfGroupAllowed(AllowedQueryOptions unused, string query, string unusedName)
{
// Arrange
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings));
}
[Theory]
[PropertyData("SupportedQueryOptions")]
public void SupportedQueryOptions_ThrowIfGroupNotAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Theory]
[PropertyData("UnsupportedQueryOptions")]
public void UnsupportedQueryOptions_SucceedIfGroupAllowed(AllowedQueryOptions unused, string query, string unusedName)
{
// Arrange
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
ODataQueryOptions option = new ODataQueryOptions(_context, message);
ODataValidationSettings settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.DoesNotThrow(() => _validator.Validate(option, settings));
}
[Theory]
[PropertyData("UnsupportedQueryOptions")]
public void UnsupportedQueryOptions_ThrowIfGroupNotAllowed(AllowedQueryOptions unused, string query, string optionName)
{
// Arrange
var message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?" + query));
var option = new ODataQueryOptions(_context, message);
var expectedMessage = string.Format(
"Query option '{0}' is not allowed. " +
"To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
optionName);
var settings = new ODataValidationSettings()
{
AllowedQueryOptions = AllowedQueryOptions.Supported,
};
// Act & Assert
Assert.Throws<ODataException>(() => _validator.Validate(option, settings), expectedMessage);
}
[Fact] [Fact]
public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull() public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull()
{ {

Просмотреть файл

@ -21,6 +21,11 @@ namespace System.Web.OData.Query.Validators
return new ODataQueryContext(GetProductsModel(), typeof(Product)); return new ODataQueryContext(GetProductsModel(), typeof(Product));
} }
internal static ODataQueryContext CreateDerivedProductsContext()
{
return new ODataQueryContext(GetDerivedProductsModel(), typeof(Product));
}
private static IEdmModel GetCustomersModel() private static IEdmModel GetCustomersModel()
{ {
HttpConfiguration configuration = new HttpConfiguration(); HttpConfiguration configuration = new HttpConfiguration();
@ -32,12 +37,27 @@ namespace System.Web.OData.Query.Validators
} }
private static IEdmModel GetProductsModel() private static IEdmModel GetProductsModel()
{
var builder = GetProductsBuilder();
return builder.GetEdmModel();
}
private static IEdmModel GetDerivedProductsModel()
{
var builder = GetProductsBuilder();
builder.EntitySet<Product>("Product");
builder.EntityType<DerivedProduct>().DerivesFrom<Product>();
builder.EntityType<DerivedCategory>().DerivesFrom<Category>();
return builder.GetEdmModel();
}
private static ODataConventionModelBuilder GetProductsBuilder()
{ {
HttpConfiguration configuration = new HttpConfiguration(); HttpConfiguration configuration = new HttpConfiguration();
configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product))); configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product)));
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration); ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration);
builder.EntitySet<Product>("Product"); builder.EntitySet<Product>("Product");
return builder.GetEdmModel(); return builder;
} }
} }
} }

Просмотреть файл

@ -127,6 +127,7 @@
<Compile Include="OData\Builder\TestModels\PropertyAlias.cs" /> <Compile Include="OData\Builder\TestModels\PropertyAlias.cs" />
<Compile Include="OData\Builder\TestModels\SpecialOrderLine.cs" /> <Compile Include="OData\Builder\TestModels\SpecialOrderLine.cs" />
<Compile Include="OData\ClrPropertyInfoAnnotationTest.cs" /> <Compile Include="OData\ClrPropertyInfoAnnotationTest.cs" />
<Compile Include="OData\EnableQueryTests.cs" />
<Compile Include="OData\ODataContainmentTest.cs" /> <Compile Include="OData\ODataContainmentTest.cs" />
<Compile Include="OData\ContentIdHelpersTest.cs" /> <Compile Include="OData\ContentIdHelpersTest.cs" />
<Compile Include="OData\DollarFormatTest.cs" /> <Compile Include="OData\DollarFormatTest.cs" />