Merge branch 'odata-v5.3-rtm' into merge.v5.3-rtm
This commit is contained in:
Коммит
ae8016bee4
|
@ -25,8 +25,8 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#if ASPNETODATA
|
||||
#if !BUILD_GENERATED_VERSION
|
||||
[assembly: AssemblyVersion("5.3.0.0")] // ASPNETODATA
|
||||
[assembly: AssemblyFileVersion("5.3.0.0")] // ASPNETODATA
|
||||
[assembly: AssemblyVersion("5.3.1.0")] // ASPNETODATA
|
||||
[assembly: AssemblyFileVersion("5.3.1.0")] // ASPNETODATA
|
||||
#endif
|
||||
[assembly: AssemblyProduct("Microsoft ASP.NET Web API OData")]
|
||||
#endif
|
|
@ -153,6 +153,9 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
case QueryNodeKind.EntityCollectionCast:
|
||||
return BindEntityCollectionCastNode(node as EntityCollectionCastNode);
|
||||
|
||||
case QueryNodeKind.CollectionFunctionCall:
|
||||
case QueryNodeKind.EntityCollectionFunctionCall:
|
||||
// Unused or have unknown uses.
|
||||
default:
|
||||
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
|
||||
}
|
||||
|
@ -198,6 +201,11 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
case QueryNodeKind.SingleEntityCast:
|
||||
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:
|
||||
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");
|
||||
}
|
||||
|
||||
ValidateFunction("all", settings);
|
||||
EnterLambda(settings);
|
||||
|
||||
try
|
||||
|
@ -105,6 +106,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
ValidateFunction("any", settings);
|
||||
EnterLambda(settings);
|
||||
|
||||
try
|
||||
|
@ -255,7 +257,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -279,7 +281,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// Validate child nodes but not the ConvertNode itself.
|
||||
ValidateQueryNode(convertNode.Source, settings);
|
||||
}
|
||||
|
||||
|
@ -300,7 +302,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
|
||||
// recursion
|
||||
if (sourceNode != null)
|
||||
|
@ -330,7 +332,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -354,7 +356,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
ValidateQueryNode(propertyAccessNode.Source, settings);
|
||||
}
|
||||
|
||||
|
@ -379,7 +381,7 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
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"));
|
||||
}
|
||||
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:
|
||||
ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings);
|
||||
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:
|
||||
ValidateAllNode(node as AllNode, settings);
|
||||
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:
|
||||
result = AllowedFunctions.IndexOf;
|
||||
break;
|
||||
case "IsOf":
|
||||
case "isof":
|
||||
result = AllowedFunctions.IsOf;
|
||||
break;
|
||||
case ClrCanonicalFunctions.LengthFunctionName:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 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
|
||||
// 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>
|
||||
/// Looks up a localized string similar to The query parameter '{0}' is not supported..
|
||||
/// </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>
|
||||
/// Looks up a localized string similar to The element type '{0}' of the given collection type '{1}' is not of the type '{2}'..
|
||||
/// </summary>
|
||||
|
|
|
@ -201,6 +201,12 @@
|
|||
<data name="QueryNodeBindingNotSupported" xml:space="preserve">
|
||||
<value>Binding OData QueryNode of kind {0} is not supported by {1}.</value>
|
||||
</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">
|
||||
<value>Duplicate property named '{0}' is not supported in '$orderby'.</value>
|
||||
</data>
|
||||
|
|
|
@ -159,6 +159,11 @@ namespace System.Web.OData.Query.Expressions
|
|||
case QueryNodeKind.EntityCollectionCast:
|
||||
return BindEntityCollectionCastNode(node as EntityCollectionCastNode);
|
||||
|
||||
case QueryNodeKind.CollectionFunctionCall:
|
||||
case QueryNodeKind.EntityCollectionFunctionCall:
|
||||
case QueryNodeKind.CollectionOpenPropertyAccess:
|
||||
case QueryNodeKind.CollectionPropertyCast:
|
||||
// Unused or have unknown uses.
|
||||
default:
|
||||
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
|
||||
}
|
||||
|
@ -207,6 +212,14 @@ namespace System.Web.OData.Query.Expressions
|
|||
case QueryNodeKind.SingleEntityFunctionCall:
|
||||
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:
|
||||
throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
ValidateFunction("all", settings);
|
||||
EnterLambda(settings);
|
||||
|
||||
try
|
||||
|
@ -110,6 +111,7 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
ValidateFunction("any", settings);
|
||||
EnterLambda(settings);
|
||||
|
||||
try
|
||||
|
@ -261,7 +263,7 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -285,7 +287,7 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// Validate child nodes but not the ConvertNode itself.
|
||||
ValidateQueryNode(convertNode.Source, settings);
|
||||
}
|
||||
|
||||
|
@ -312,8 +314,6 @@ namespace System.Web.OData.Query.Validators
|
|||
throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, navigationProperty.Name));
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
|
||||
// recursion
|
||||
if (sourceNode != null)
|
||||
{
|
||||
|
@ -342,7 +342,7 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
// No default validation logic here.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -366,14 +366,13 @@ namespace System.Web.OData.Query.Validators
|
|||
throw Error.ArgumentNull("settings");
|
||||
}
|
||||
|
||||
// Check whether the property is not filterable
|
||||
// Check whether the property is filterable.
|
||||
IEdmProperty property = propertyAccessNode.Property;
|
||||
if (EdmLibHelpers.IsNotFilterable(property, _model))
|
||||
{
|
||||
throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name));
|
||||
}
|
||||
|
||||
// no default validation logic here
|
||||
ValidateQueryNode(propertyAccessNode.Source, settings);
|
||||
}
|
||||
|
||||
|
@ -398,7 +397,13 @@ namespace System.Web.OData.Query.Validators
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
/// Override this method to validate the Not operator.
|
||||
/// </summary>
|
||||
|
@ -453,6 +487,9 @@ namespace System.Web.OData.Query.Validators
|
|||
throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators"));
|
||||
}
|
||||
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:
|
||||
ValidateEntityCollectionCastNode(node as EntityCollectionCastNode, settings);
|
||||
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);
|
||||
break;
|
||||
|
||||
case QueryNodeKind.SingleEntityFunctionCall:
|
||||
ValidateSingleEntityFunctionCallNode((SingleEntityFunctionCallNode)node, settings);
|
||||
break;
|
||||
|
||||
case QueryNodeKind.SingleNavigationNode:
|
||||
SingleNavigationNode navigationNode = node as SingleNavigationNode;
|
||||
ValidateNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty, settings);
|
||||
|
@ -626,6 +675,17 @@ namespace System.Web.OData.Query.Validators
|
|||
case QueryNodeKind.All:
|
||||
ValidateAllNode(node as AllNode, settings);
|
||||
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:
|
||||
result = AllowedFunctions.IndexOf;
|
||||
break;
|
||||
case "IsOf":
|
||||
case "isof":
|
||||
result = AllowedFunctions.IsOf;
|
||||
break;
|
||||
case ClrCanonicalFunctions.LengthFunctionName:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 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
|
||||
// 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>
|
||||
/// Looks up a localized string similar to The query parameter '{0}' is not supported..
|
||||
/// </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>
|
||||
/// Looks up a localized string similar to The element type '{0}' of the given collection type '{1}' is not of the type '{2}'..
|
||||
/// </summary>
|
||||
|
|
|
@ -198,6 +198,12 @@
|
|||
<data name="QueryNodeBindingNotSupported" xml:space="preserve">
|
||||
<value>Binding OData QueryNode of kind {0} is not supported by {1}.</value>
|
||||
</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">
|
||||
<value>Duplicate property named '{0}' is not supported in '$orderby'.</value>
|
||||
</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 string QuantityPerUnit { get; set; }
|
||||
public decimal? UnitPrice { get; set; }
|
||||
public double? Weight { get; set; }
|
||||
public short? UnitsInStock { get; set; }
|
||||
public short? UnitsOnOrder { get; set; }
|
||||
|
||||
|
|
|
@ -384,7 +384,7 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
var filters = VerifyQueryDeserialization<DataTypes>(filter);
|
||||
var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value });
|
||||
|
||||
Assert.Equal(result, expectedResult);
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
// Issue: 477
|
||||
|
@ -1380,6 +1380,649 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
|
||||
#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]
|
||||
[InlineData("UShortProp eq 12", "$it => (Convert($it.UShortProp) == 12)")]
|
||||
[InlineData("ULongProp eq 12L", "$it => (Convert($it.ULongProp) == 12)")]
|
||||
|
@ -1542,7 +2185,7 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(RunFilter(filterWithNullPropagation, product), expectedValue.WithNullPropagation);
|
||||
Assert.Equal(expectedValue.WithNullPropagation, RunFilter(filterWithNullPropagation, product));
|
||||
}
|
||||
|
||||
var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>;
|
||||
|
@ -1552,7 +2195,7 @@ namespace System.Web.Http.OData.Query.Expressions
|
|||
}
|
||||
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();
|
||||
model.EntitySet<T>("Products");
|
||||
if (key == typeof(Product))
|
||||
{
|
||||
model.Entity<DerivedProduct>().DerivesFrom<Product>();
|
||||
model.Entity<DerivedCategory>().DerivesFrom<Category>();
|
||||
}
|
||||
|
||||
value = _modelCache[key] = model.GetEdmModel();
|
||||
}
|
||||
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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Data.OData;
|
||||
using Microsoft.TestCommon;
|
||||
|
@ -18,6 +20,35 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
_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]
|
||||
public void ValidateThrowsOnNullOption()
|
||||
{
|
||||
|
@ -32,64 +63,171 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
_validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)]
|
||||
[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)
|
||||
[Fact]
|
||||
public void QueryOptionDataSets_CoverAllValues()
|
||||
{
|
||||
// Arrange
|
||||
HttpRequestMessage message = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
|
||||
);
|
||||
ODataQueryOptions option = new ODataQueryOptions(_context, message);
|
||||
ODataValidationSettings settings = new ODataValidationSettings()
|
||||
{
|
||||
AllowedQueryOptions = AllowedQueryOptions.All & ~queryOption
|
||||
};
|
||||
// Get all values in the AllowedQueryOptions enum.
|
||||
var values = new HashSet<AllowedQueryOptions>(
|
||||
Enum.GetValues(typeof(AllowedQueryOptions)).Cast<AllowedQueryOptions>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ODataException>(() => _validator.Validate(option, settings));
|
||||
Assert.Equal(
|
||||
"Query option '" + queryOptionName + "' is not allowed. To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
|
||||
exception.Message,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
var groupValues = new[]
|
||||
{
|
||||
AllowedQueryOptions.All,
|
||||
AllowedQueryOptions.None,
|
||||
AllowedQueryOptions.Supported,
|
||||
};
|
||||
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]
|
||||
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)]
|
||||
[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_DoesNotThrow_ForAllowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
|
||||
[PropertyData("SupportedQueryOptions")]
|
||||
[PropertyData("UnsupportedQueryOptions")]
|
||||
public void AllowedQueryOptions_SucceedIfAllowed(AllowedQueryOptions allow, string query, string unused)
|
||||
{
|
||||
// Arrange
|
||||
HttpRequestMessage message = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
|
||||
);
|
||||
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
|
||||
ODataQueryOptions option = new ODataQueryOptions(_context, message);
|
||||
ODataValidationSettings settings = new ODataValidationSettings()
|
||||
{
|
||||
AllowedQueryOptions = queryOption
|
||||
AllowedQueryOptions = allow,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
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]
|
||||
public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull()
|
||||
{
|
||||
|
|
|
@ -20,6 +20,11 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
return new ODataQueryContext(GetProductsModel(), typeof(Product));
|
||||
}
|
||||
|
||||
internal static ODataQueryContext CreateDerivedProductsContext()
|
||||
{
|
||||
return new ODataQueryContext(GetDerivedProductsModel(), typeof(Product));
|
||||
}
|
||||
|
||||
private static IEdmModel GetCustomersModel()
|
||||
{
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
|
@ -31,12 +36,27 @@ namespace System.Web.Http.OData.Query.Validators
|
|||
}
|
||||
|
||||
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();
|
||||
configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product)));
|
||||
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration);
|
||||
builder.EntitySet<Product>("Product");
|
||||
return builder.GetEdmModel();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@
|
|||
<Compile Include="OData\EdmComplexCollectionObjectTest.cs" />
|
||||
<Compile Include="OData\EdmEntityCollectionObjectTest.cs" />
|
||||
<Compile Include="OData\EdmStructuredObjectTest.cs" />
|
||||
<Compile Include="OData\EnableQueryTest.cs" />
|
||||
<Compile Include="OData\FastPropertyAccessorTest.cs" />
|
||||
<Compile Include="OData\Formatter\ClrTypeCacheTest.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 DateTimeOffset? DiscontinuedDate { get; set; }
|
||||
public DateTimeOffset NonNullableDiscontinuedDate { get; set; }
|
||||
[NotFilterable]
|
||||
public DateTimeOffset NotFilterableDiscontinuedDate { get; set; }
|
||||
|
||||
public DateTimeOffset DiscontinuedOffset { get; set; }
|
||||
public TimeSpan DiscontinuedSince { get; set; }
|
||||
|
||||
public ushort? UnsignedReorderLevel { get; set; }
|
||||
|
||||
public SimpleEnum Ranking { get; set; }
|
||||
|
||||
public Category Category { get; set; }
|
||||
|
||||
public Address SupplierAddress { get; set; }
|
||||
|
||||
public int[] AlternateIDs { get; set; }
|
||||
public Address[] AlternateAddresses { get; set; }
|
||||
[NotFilterable]
|
||||
public Address[] NotFilterableAlternateAddresses { get; set; }
|
||||
}
|
||||
|
||||
public class Category
|
||||
|
|
|
@ -6,7 +6,6 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Dispatcher;
|
||||
using System.Web.OData.Builder;
|
||||
using System.Xml.Linq;
|
||||
|
@ -425,7 +424,7 @@ namespace System.Web.OData.Query.Expressions
|
|||
var filters = VerifyQueryDeserialization<DataTypes>(filter);
|
||||
var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value });
|
||||
|
||||
Assert.Equal(result, expectedResult);
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
// 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)")]
|
||||
public void CastMethod_Succeeds(string filter, string expectedResult)
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
VerifyQueryDeserialization<DataTypes>(
|
||||
filter,
|
||||
expectedResult,
|
||||
|
@ -1566,23 +1566,113 @@ namespace System.Web.OData.Query.Expressions
|
|||
}
|
||||
|
||||
[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(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.")]
|
||||
public void CastFails_UndefinedSourceOrTarget_Throws(string filter, string errorMessage)
|
||||
[InlineData("cast(NoSuchProperty,Edm.Int32) ne null",
|
||||
"Could not find a property named 'NoSuchProperty' on type 'System.Web.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> 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]
|
||||
[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(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 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.
|
||||
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(StringProp,Microsoft.TestCommon.Types.SimpleEnum) 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)");
|
||||
}
|
||||
|
||||
// See OtherFunctions_SomeTwoParameterCasts_ThrowODataException and OtherFunctions_SomeSingleParameterCasts_ThrowODataException
|
||||
// in FilterQueryValidatorTest. ODL's ODataQueryOptionParser and FunctionCallBinder call the code throwing these exceptions.
|
||||
[Theory]
|
||||
[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",
|
||||
"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.
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#region parameter alias for filter query option
|
||||
|
@ -1979,7 +2668,7 @@ namespace System.Web.OData.Query.Expressions
|
|||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(RunFilter(filterWithNullPropagation, product), expectedValue.WithNullPropagation);
|
||||
Assert.Equal(expectedValue.WithNullPropagation, RunFilter(filterWithNullPropagation, product));
|
||||
}
|
||||
|
||||
var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>;
|
||||
|
@ -1989,7 +2678,7 @@ namespace System.Web.OData.Query.Expressions
|
|||
}
|
||||
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();
|
||||
model.EntitySet<T>("Products");
|
||||
if (key == typeof(Product))
|
||||
{
|
||||
model.EntityType<DerivedProduct>().DerivesFrom<Product>();
|
||||
model.EntityType<DerivedCategory>().DerivesFrom<Category>();
|
||||
}
|
||||
|
||||
value = _modelCache[key] = model.GetEdmModel();
|
||||
}
|
||||
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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.OData.Core;
|
||||
using Microsoft.TestCommon;
|
||||
|
@ -18,6 +20,35 @@ namespace System.Web.OData.Query.Validators
|
|||
_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]
|
||||
public void ValidateThrowsOnNullOption()
|
||||
{
|
||||
|
@ -32,64 +63,171 @@ namespace System.Web.OData.Query.Validators
|
|||
_validator.Validate(new ODataQueryOptions(_context, new HttpRequestMessage()), null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)]
|
||||
[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)
|
||||
[Fact]
|
||||
public void QueryOptionDataSets_CoverAllValues()
|
||||
{
|
||||
// Arrange
|
||||
HttpRequestMessage message = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
|
||||
);
|
||||
ODataQueryOptions option = new ODataQueryOptions(_context, message);
|
||||
ODataValidationSettings settings = new ODataValidationSettings()
|
||||
{
|
||||
AllowedQueryOptions = AllowedQueryOptions.All & ~queryOption
|
||||
};
|
||||
// Get all values in the AllowedQueryOptions enum.
|
||||
var values = new HashSet<AllowedQueryOptions>(
|
||||
Enum.GetValues(typeof(AllowedQueryOptions)).Cast<AllowedQueryOptions>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ODataException>(() => _validator.Validate(option, settings));
|
||||
Assert.Equal(
|
||||
"Query option '" + queryOptionName + "' is not allowed. To allow it, set the 'AllowedQueryOptions' property on EnableQueryAttribute or QueryValidationSettings.",
|
||||
exception.Message,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
var groupValues = new[]
|
||||
{
|
||||
AllowedQueryOptions.All,
|
||||
AllowedQueryOptions.None,
|
||||
AllowedQueryOptions.Supported,
|
||||
};
|
||||
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]
|
||||
[InlineData("filter", "Name eq 'abc'", AllowedQueryOptions.Filter)]
|
||||
[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_DoesNotThrow_ForAllowedQueryOptions(string queryOptionName, string queryValue, AllowedQueryOptions queryOption)
|
||||
[PropertyData("SupportedQueryOptions")]
|
||||
[PropertyData("UnsupportedQueryOptions")]
|
||||
public void AllowedQueryOptions_SucceedIfAllowed(AllowedQueryOptions allow, string query, string unused)
|
||||
{
|
||||
// Arrange
|
||||
HttpRequestMessage message = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
new Uri("http://localhost/?$" + queryOptionName + "=" + queryValue)
|
||||
);
|
||||
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/?$" + query));
|
||||
ODataQueryOptions option = new ODataQueryOptions(_context, message);
|
||||
ODataValidationSettings settings = new ODataValidationSettings()
|
||||
{
|
||||
AllowedQueryOptions = queryOption
|
||||
AllowedQueryOptions = allow,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
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]
|
||||
public void Validate_ValidatesSelectExpandQueryOption_IfItIsNotNull()
|
||||
{
|
||||
|
|
|
@ -21,6 +21,11 @@ namespace System.Web.OData.Query.Validators
|
|||
return new ODataQueryContext(GetProductsModel(), typeof(Product));
|
||||
}
|
||||
|
||||
internal static ODataQueryContext CreateDerivedProductsContext()
|
||||
{
|
||||
return new ODataQueryContext(GetDerivedProductsModel(), typeof(Product));
|
||||
}
|
||||
|
||||
private static IEdmModel GetCustomersModel()
|
||||
{
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
|
@ -32,12 +37,27 @@ namespace System.Web.OData.Query.Validators
|
|||
}
|
||||
|
||||
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();
|
||||
configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(typeof(Product)));
|
||||
ODataConventionModelBuilder builder = new ODataConventionModelBuilder(configuration);
|
||||
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\SpecialOrderLine.cs" />
|
||||
<Compile Include="OData\ClrPropertyInfoAnnotationTest.cs" />
|
||||
<Compile Include="OData\EnableQueryTests.cs" />
|
||||
<Compile Include="OData\ODataContainmentTest.cs" />
|
||||
<Compile Include="OData\ContentIdHelpersTest.cs" />
|
||||
<Compile Include="OData\DollarFormatTest.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче