746 lines
28 KiB
C#
Raw Normal View History

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Linq.Expressions;
using Microsoft.TestCommon;
using Xunit;
using Xunit.Extensions;
using Assert = Microsoft.TestCommon.AssertEx;
namespace System.Web.Http.Query
{
public class ODataQueryDeserializerTests
{
[Fact]
public void SimpleMultipartQuery()
{
VerifyQueryDeserialization(
"$filter=ProductName eq 'Doritos'&$orderby=UnitPrice&$top=100",
"Where(Param_0 => (Param_0.ProductName == \"Doritos\")).OrderBy(Param_1 => Param_1.UnitPrice).Take(100)");
}
#region Ordering
[Fact]
public void OrderBy()
{
VerifyQueryDeserialization(
"$orderby=UnitPrice",
"OrderBy(Param_0 => Param_0.UnitPrice)");
}
[Fact]
public void OrderByAscending()
{
VerifyQueryDeserialization(
"$orderby=UnitPrice asc",
"OrderBy(Param_0 => Param_0.UnitPrice)");
}
[Fact]
public void OrderByDescending()
{
VerifyQueryDeserialization(
"$orderby=UnitPrice desc",
"OrderByDescending(Param_0 => Param_0.UnitPrice)");
}
[Fact]
public void OrderByAscendingThenDesscending()
{
VerifyQueryDeserialization(
"$orderby=UnitPrice desc, ProductName asc",
"OrderByDescending(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)");
}
[Theory]
[InlineData("UnitPrice desc, ProductName asc", "OrderByDescending(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)")]
[InlineData("UnitPrice desc, ProductName desc", "OrderByDescending(Param_0 => Param_0.UnitPrice).ThenByDescending(Param_0 => Param_0.ProductName)")]
[InlineData("UnitPrice , ProductName ", "OrderBy(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)")]
[InlineData("Discontinued, UnitsOnOrder, DiscontinuedDate desc", "OrderBy(Param_0 => Param_0.Discontinued).ThenBy(Param_0 => Param_0.UnitsOnOrder).ThenByDescending(Param_0 => Param_0.DiscontinuedDate)")]
public void MultipleOrderBy(string clause, string expressionResult)
{
VerifyQueryDeserialization(
"$orderby=" + clause,
expressionResult);
}
#endregion
#region Inequalities
[Fact]
public void EqualityOperator()
{
VerifyQueryDeserialization(
"$filter=ProductName eq 'Doritos'",
"Where(Param_0 => (Param_0.ProductName == \"Doritos\"))");
}
[Fact]
public void NotEqualOperator()
{
VerifyQueryDeserialization(
"$filter=ProductName ne 'Doritos'",
"Where(Param_0 => (Param_0.ProductName != \"Doritos\"))");
}
[Fact]
public void GreaterThanOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice gt 5.00",
"Where(Param_0 => (Param_0.UnitPrice > 5.00))");
}
[Fact]
public void GreaterThanEqualOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice ge 5.00",
"Where(Param_0 => (Param_0.UnitPrice >= 5.00))");
}
[Fact]
public void LessThanOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice lt 5.00",
"Where(Param_0 => (Param_0.UnitPrice < 5.00))");
}
[Fact]
public void LessThanOrEqualOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice le 5.00",
"Where(Param_0 => (Param_0.UnitPrice <= 5.00))");
}
[Fact]
public void NegativeNumbers()
{
VerifyQueryDeserialization(
"$filter=UnitPrice le -5.00",
"Where(Param_0 => (Param_0.UnitPrice <= -5.00))");
}
[Theory]
[InlineData("DateTimeProp eq datetime'2000-12-12T12:00'", "Where(Param_0 => (Param_0.DateTimeProp == 12/12/2000 12:00:00 PM))")]
[InlineData("DateTimeOffsetProp eq datetimeoffset'2002-10-10T17:00:00Z'", "Where(Param_0 => (Param_0.DateTimeOffsetProp == 10/10/2002 5:00:00 PM +00:00))")]
[InlineData("DateTimeOffsetProp eq DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp == Param_0.DateTimeOffsetProp))")]
[InlineData("DateTimeOffsetProp ne DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp != Param_0.DateTimeOffsetProp))")]
[InlineData("DateTimeOffsetProp ge DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp >= Param_0.DateTimeOffsetProp))")]
[InlineData("DateTimeOffsetProp le DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp <= Param_0.DateTimeOffsetProp))")]
public void DateInEqualities(string clause, string expectedExpression)
{
VerifyQueryDeserialization<DataTypes>(
"$filter=" + clause,
expectedExpression);
}
#endregion
#region Logical Operators
[Fact]
public void OrOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice eq 5.00 or UnitPrice eq 10.00",
"Where(Param_0 => ((Param_0.UnitPrice == 5.00) OrElse (Param_0.UnitPrice == 10.00)))");
}
[Fact]
public void AndOperator()
{
VerifyQueryDeserialization(
"$filter=UnitPrice eq 5.00 and UnitPrice eq 10.00",
"Where(Param_0 => ((Param_0.UnitPrice == 5.00) AndAlso (Param_0.UnitPrice == 10.00)))");
}
[Fact]
public void Negation()
{
VerifyQueryDeserialization(
"$filter=not (UnitPrice eq 5.00)",
"Where(Param_0 => Not((Param_0.UnitPrice == 5.00)))");
}
[Fact]
public void BoolNegation()
{
VerifyQueryDeserialization(
"$filter=not Discontinued",
"Where(Param_0 => Not(Param_0.Discontinued))");
}
[Fact]
public void NestedNegation()
{
VerifyQueryDeserialization(
"$filter=not (not(not (Discontinued)))",
"Where(Param_0 => Not(Not(Not(Param_0.Discontinued))))");
}
#endregion
#region Arithmetic Operators
[Fact]
public void Subtraction()
{
VerifyQueryDeserialization(
"$filter=UnitPrice sub 1.00 lt 5.00",
"Where(Param_0 => ((Param_0.UnitPrice - 1.00) < 5.00))");
}
[Fact]
public void Addition()
{
VerifyQueryDeserialization(
"$filter=UnitPrice add 1.00 lt 5.00",
"Where(Param_0 => ((Param_0.UnitPrice + 1.00) < 5.00))");
}
[Fact]
public void Multiplication()
{
VerifyQueryDeserialization(
"$filter=UnitPrice mul 1.00 lt 5.00",
"Where(Param_0 => ((Param_0.UnitPrice * 1.00) < 5.00))");
}
[Fact]
public void Division()
{
VerifyQueryDeserialization(
"$filter=UnitPrice div 1.00 lt 5.00",
"Where(Param_0 => ((Param_0.UnitPrice / 1.00) < 5.00))");
}
[Fact]
public void Modulo()
{
VerifyQueryDeserialization(
"$filter=UnitPrice mod 1.00 lt 5.00",
"Where(Param_0 => ((Param_0.UnitPrice % 1.00) < 5.00))");
}
#endregion
[Fact]
public void Grouping()
{
VerifyQueryDeserialization(
"$filter=((ProductName ne 'Doritos') or (UnitPrice lt 5.00))",
"Where(Param_0 => ((Param_0.ProductName != \"Doritos\") OrElse (Param_0.UnitPrice < 5.00)))");
}
[Fact]
public void MemberExpressions()
{
VerifyQueryDeserialization(
"$filter=Category/CategoryName eq 'Snacks'",
"Where(Param_0 => (Param_0.Category.CategoryName == \"Snacks\"))");
}
#region String Functions
[Fact]
public void StringSubstringOf()
{
// In OData, the order of parameters is actually reversed in the resulting
// string.Contains expression
VerifyQueryDeserialization(
"$filter=substringof('Abc', ProductName) eq true",
"Where(Param_0 => (Param_0.ProductName.Contains(\"Abc\") == True))");
VerifyQueryDeserialization(
"$filter=substringof(ProductName, 'Abc') eq true",
"Where(Param_0 => (\"Abc\".Contains(Param_0.ProductName) == True))");
}
[Fact]
public void StringStartsWith()
{
VerifyQueryDeserialization(
"$filter=startswith(ProductName, 'Abc') eq true",
"Where(Param_0 => (Param_0.ProductName.StartsWith(\"Abc\") == True))");
}
[Fact]
public void StringEndsWith()
{
VerifyQueryDeserialization(
"$filter=endswith(ProductName, 'Abc') eq true",
"Where(Param_0 => (Param_0.ProductName.EndsWith(\"Abc\") == True))");
}
[Fact]
public void StringLength()
{
VerifyQueryDeserialization(
"$filter=length(ProductName) gt 0",
"Where(Param_0 => (Param_0.ProductName.Length > 0))");
}
[Fact]
public void StringIndexOf()
{
VerifyQueryDeserialization(
"$filter=indexof(ProductName, 'Abc') eq 5",
"Where(Param_0 => (Param_0.ProductName.IndexOf(\"Abc\") == 5))");
}
[Fact]
public void StringReplace()
{
VerifyQueryDeserialization(
"$filter=replace(ProductName, 'Abc', 'Def') eq 'FooDef'",
"Where(Param_0 => (Param_0.ProductName.Replace(\"Abc\", \"Def\") == \"FooDef\"))");
}
[Fact]
public void StringSubstring()
{
VerifyQueryDeserialization(
"$filter=substring(ProductName, 3) eq 'uctName'",
"Where(Param_0 => (Param_0.ProductName.Substring(3) == \"uctName\"))");
VerifyQueryDeserialization(
"$filter=substring(ProductName, 3, 4) eq 'uctN'",
"Where(Param_0 => (Param_0.ProductName.Substring(3, 4) == \"uctN\"))");
}
[Fact]
public void StringToLower()
{
VerifyQueryDeserialization(
"$filter=tolower(ProductName) eq 'tasty treats'",
"Where(Param_0 => (Param_0.ProductName.ToLower() == \"tasty treats\"))");
}
[Fact]
public void StringToUpper()
{
VerifyQueryDeserialization(
"$filter=toupper(ProductName) eq 'TASTY TREATS'",
"Where(Param_0 => (Param_0.ProductName.ToUpper() == \"TASTY TREATS\"))");
}
[Fact]
public void StringTrim()
{
VerifyQueryDeserialization(
"$filter=trim(ProductName) eq 'Tasty Treats'",
"Where(Param_0 => (Param_0.ProductName.Trim() == \"Tasty Treats\"))");
}
[Fact]
public void StringConcat()
{
VerifyQueryDeserialization(
"$filter=concat('Foo', 'Bar') eq 'FooBar'",
"Where(Param_0 => (Concat(\"Foo\", \"Bar\") == \"FooBar\"))");
}
#endregion
#region Date Functions
[Fact]
public void DateDay()
{
VerifyQueryDeserialization(
"$filter=day(DiscontinuedDate) eq 8",
"Where(Param_0 => (Param_0.DiscontinuedDate.Day == 8))");
}
[Fact]
public void DateMonth()
{
VerifyQueryDeserialization(
"$filter=month(DiscontinuedDate) eq 8",
"Where(Param_0 => (Param_0.DiscontinuedDate.Month == 8))");
}
[Fact]
public void DateYear()
{
VerifyQueryDeserialization(
"$filter=year(DiscontinuedDate) eq 1974",
"Where(Param_0 => (Param_0.DiscontinuedDate.Year == 1974))");
}
[Fact]
public void DateHour()
{
VerifyQueryDeserialization("$filter=hour(DiscontinuedDate) eq 8",
"Where(Param_0 => (Param_0.DiscontinuedDate.Hour == 8))");
}
[Fact]
public void DateMinute()
{
VerifyQueryDeserialization(
"$filter=minute(DiscontinuedDate) eq 12",
"Where(Param_0 => (Param_0.DiscontinuedDate.Minute == 12))");
}
[Fact]
public void DateSecond()
{
VerifyQueryDeserialization(
"$filter=second(DiscontinuedDate) eq 33",
"Where(Param_0 => (Param_0.DiscontinuedDate.Second == 33))");
}
#endregion
#region Math Functions
[Fact]
public void MathRound()
{
VerifyQueryDeserialization(
"$filter=round(UnitPrice) gt 5.00",
"Where(Param_0 => (Round(Param_0.UnitPrice) > 5.00))");
}
[Fact]
public void MathFloor()
{
VerifyQueryDeserialization(
"$filter=floor(UnitPrice) eq 5",
"Where(Param_0 => (Floor(Param_0.UnitPrice) == 5))");
}
[Fact]
public void MathCeiling()
{
VerifyQueryDeserialization(
"$filter=ceiling(UnitPrice) eq 5",
"Where(Param_0 => (Ceiling(Param_0.UnitPrice) == 5))");
}
#endregion
#region Data Types
[Fact]
public void GuidExpression()
{
VerifyQueryDeserialization<DataTypes>(
"$filter=GuidProp eq guid'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
"Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
// verify case insensitivity
VerifyQueryDeserialization<DataTypes>(
"$filter=GuidProp eq GuiD'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
"Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
}
[Fact]
public void DateTimeExpression()
{
VerifyQueryDeserialization<DataTypes>(
"$filter=DateTimeProp lt datetime'2000-12-12T12:00'",
"Where(Param_0 => (Param_0.DateTimeProp < 12/12/2000 12:00:00 PM))");
}
[Fact]
public void DateTimeOffsetExpression()
{
VerifyQueryDeserialization<DataTypes>(
"$filter=DateTimeOffsetProp ge datetimeoffset'2002-10-10T17:00:00Z'",
"Where(Param_0 => (Param_0.DateTimeOffsetProp >= 10/10/2002 5:00:00 PM +00:00))");
}
[Fact]
public void TimeExpression()
{
VerifyQueryDeserialization<DataTypes>(
"$filter=TimeSpanProp ge time'13:20:00'",
"Where(Param_0 => (Param_0.TimeSpanProp >= 13:20:00))");
// verify parse error for invalid literal format
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'invalid'", String.Empty);
},
"Parse error in $filter. String was not recognized as a valid TimeSpan. (at index 20)");
}
[Fact]
public void BinaryExpression()
{
string binary = "23ABFF";
byte[] bytes = new byte[] {
byte.Parse("23", Globalization.NumberStyles.HexNumber),
byte.Parse("AB", Globalization.NumberStyles.HexNumber),
byte.Parse("FF", Globalization.NumberStyles.HexNumber)
};
VerifyQueryDeserialization<DataTypes>(
String.Format("$filter=ByteArrayProp eq binary'{0}'", binary),
"Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
q =>
{
// verify that the binary data was deserialized into a constant expression of type byte[]
LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
BinaryExpression bex = (BinaryExpression)lex.Body;
byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
Assert.True(actualBytes.SequenceEqual(bytes));
});
// test alternate 'X' syntax
VerifyQueryDeserialization<DataTypes>(
String.Format("$filter=ByteArrayProp eq X'{0}'", binary),
"Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
q =>
{
// verify that the binary data was deserialized into a constant expression of type byte[]
LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
BinaryExpression bex = (BinaryExpression)lex.Body;
byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
Assert.True(actualBytes.SequenceEqual(bytes));
});
// verify parse error for invalid literal format
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>(
String.Format("$filter=ByteArrayProp eq binary'{0}'", "WXYZ"), String.Empty);
},
"Parse error in $filter. Input string was not in a correct format. (at index 23)");
// verify parse error for invalid hex literal (odd hex strings are not supported)
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>(
String.Format("$filter=ByteArrayProp eq binary'23A'", "XYZ"), String.Empty);
},
"Parse error in $filter. Invalid hexadecimal literal. (at index 23)");
}
[Fact]
public void IntegerLiteralSuffix()
{
// long L
VerifyQueryDeserialization<DataTypes>(
"$filter=LongProp lt 987654321L and LongProp gt 123456789l",
"Where(Param_0 => ((Param_0.LongProp < 987654321) AndAlso (Param_0.LongProp > 123456789)))");
VerifyQueryDeserialization<DataTypes>(
"$filter=LongProp lt -987654321L and LongProp gt -123456789l",
"Where(Param_0 => ((Param_0.LongProp < -987654321) AndAlso (Param_0.LongProp > -123456789)))");
}
[Fact]
public void RealLiteralSuffixes()
{
// Float F
VerifyQueryDeserialization<DataTypes>(
"$filter=FloatProp lt 4321.56F and FloatProp gt 1234.56f",
"Where(Param_0 => ((Param_0.FloatProp < 4321.56) AndAlso (Param_0.FloatProp > 1234.56)))");
// Decimal M
VerifyQueryDeserialization<DataTypes>(
"$filter=DecimalProp lt 4321.56M and DecimalProp gt 1234.56m",
"Where(Param_0 => ((Param_0.DecimalProp < 4321.56) AndAlso (Param_0.DecimalProp > 1234.56)))");
}
[Theory]
[InlineData("'hello,world'", "hello,world")]
[InlineData("'''hello,world'", "'hello,world")]
[InlineData("'hello,world'''", "hello,world'")]
[InlineData("'hello,''wor''ld'", "hello,'wor'ld")]
[InlineData("'hello,''''''world'", "hello,'''world")]
[InlineData("'\"hello,world\"'", "\"hello,world\"")]
[InlineData("'\"hello,world'", "\"hello,world")]
[InlineData("'hello,world\"'", "hello,world\"")]
[InlineData("'hello,\"world'", "hello,\"world")]
[InlineData("'México D.F.'", "México D.F.")]
public void StringLiterals(string literal, string expected)
{
VerifyQueryDeserialization<Product>(
"$filter=ProductName eq " + literal,
String.Format("Where(Param_0 => (Param_0.ProductName == \"{0}\"))", expected));
}
#endregion
#region Negative tests
[Theory]
[InlineData('+')]
[InlineData('*')]
[InlineData('%')]
[InlineData('[')]
[InlineData(']')]
public void InvalidCharactersInQuery_TokenizerFails(char ch)
{
Assert.Throws<ParseException>(delegate
{
string filter = String.Format("2 {0} 3", ch);
VerifyQueryDeserialization<DataTypes>("$filter=" + Uri.EscapeDataString(filter), String.Empty);
},
String.Format("Parse error in $filter. Syntax error '{0}' (at index 2)", ch));
}
[Fact]
public void InvalidTypeCreationExpression()
{
// underminated string literal
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'13:20:00", String.Empty);
},
"Parse error in $filter. Unterminated string literal (at index 29)");
// use of parens rather than quotes
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time(13:20:00)", String.Empty);
},
"Parse error in $filter. Invalid 'time' type creation expression. (at index 16)");
// verify the exception returned when type expression that isn't
// one of the supported keyword types is used. In this case it falls
// through as a member expression
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization("$filter=math'123' eq true", String.Empty);
},
"Parse error in $filter. No property or field 'math' exists in type 'Product' (at index 0)");
}
[Fact]
public void InvalidMethodCall()
{
// incorrect casing of supported method
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization("$filter=Startswith(ProductName, 'Abc') eq true", String.Empty);
},
"Parse error in $filter. Unknown identifier 'Startswith' (at index 0)");
// attempt to access a method defined on the entity type
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization<DataTypes>("$filter=Inaccessable() eq \"Bar\"", String.Empty);
},
"Parse error in $filter. Unknown identifier 'Inaccessable' (at index 0)");
// verify that Type methods like string.PadLeft, etc. are not supported.
Assert.Throws<ParseException>(delegate
{
VerifyQueryDeserialization("$filter=ProductName/PadLeft(100000000000000000000) eq \"Foo\"", String.Empty);
},
"Parse error in $filter. Unknown identifier 'PadLeft' (at index 12)");
}
[Fact]
public void InvalidQueryParameterToTop()
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$top=-42", String.Empty),
"The OData query parameter '$top' has an invalid value. The value should be a positive integer. The provided value was '-42'");
}
[Fact]
public void InvalidQueryParameterToSkip()
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$skip=-42", String.Empty),
"The OData query parameter '$skip' has an invalid value. The value should be a positive integer. The provided value was '-42'");
}
[Fact]
public void InvalidProperty_NotExists()
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$filter=length mod n(ProductName) eq 12", String.Empty),
"Parse error in $filter. No property or field 'length' exists in type 'Product' (at index 0)");
}
[Fact]
public void InvalidFunctionCall_EmptyArguments()
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$filter=length() eq 12", String.Empty),
"Parse error in $filter. No applicable method 'Length' exists in type 'System.String' (at index 0)");
}
[Theory]
[InlineData("(2 add 3 eq 2", 13)]
[InlineData("(2 add (3) eq 2", 15)]
[InlineData("(((( 2 eq 2", 11)]
public void Missing_Parantheses(string clause, int index)
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
String.Format("Parse error in $filter. ')' or operator expected (at index {0})", index));
}
[Theory]
[InlineData("'hello,world", 12)]
[InlineData("'''hello,world", 14)]
[InlineData("'hello,world''", 14)]
[InlineData("'hello,''wor''ld", 16)]
public void UnterminatedStringLiterals(string clause, int index)
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
String.Format("Parse error in $filter. Unterminated string literal (at index {0})", index));
}
[Theory]
[InlineData("\"hello,world\"", 0)]
public void InvalidStringLiterals(string clause, int index)
{
Assert.Throws<ParseException>(
() => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
String.Format("Parse error in $filter. Syntax error '\"' (at index {0})", index));
}
#endregion
[Fact(DisplayName = "ODataQueryDeserializer is internal.")]
public void TypeIsCorrect()
{
Assert.Type.HasProperties(typeof(ODataQueryDeserializer), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsClass);
}
/// <summary>
/// Call the query deserializer and verify the results
/// </summary>
/// <param name="queryString">The URL query string to deserialize (e.g. $filter=ProductName eq 'Doritos')</param>
/// <param name="expectedResult">The Expression.ToString() representation of the expected result (e.g. Where(Param_0 => (Param_0.ProductName == \"Doritos\"))</param>
private void VerifyQueryDeserialization(string queryString, string expectedResult)
{
VerifyQueryDeserialization<Product>(queryString, expectedResult, null);
}
private void VerifyQueryDeserialization<T>(string queryString, string expectedResult)
{
VerifyQueryDeserialization<T>(queryString, expectedResult, null);
}
private void VerifyQueryDeserialization<T>(string queryString, string expectedResult, Action<IQueryable<T>> verify)
{
string uri = "http://myhost/odata.svc/Get?" + queryString;
IQueryable<T> baseQuery = new T[0].AsQueryable();
IQueryable<T> resultQuery = (IQueryable<T>)ODataQueryDeserializer.Deserialize(baseQuery, new Uri(uri));
VerifyExpression(resultQuery, expectedResult);
if (verify != null)
{
verify(resultQuery);
}
QueryValidator.Instance.Validate(resultQuery);
}
private void VerifyExpression(IQueryable query, string expectedExpression)
{
// strip off the beginning part of the expression to get to the first
// actual query operator
string resultExpression = query.Expression.ToString();
int startIdx = (query.ElementType.FullName + "[]").Length + 1;
resultExpression = resultExpression.Substring(startIdx);
Assert.True(resultExpression == expectedExpression,
String.Format("Expected expression '{0}' but the deserializer produced '{1}'", expectedExpression, resultExpression));
}
}
}