PageRenderTime 1651ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/test/System.Web.Http.Test/Query/ODataQueryDeserializerTests.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 743 lines | 625 code | 91 blank | 27 comment | 3 complexity | fc70fe8a73dae5d1c08930499c0dec04 MD5 | raw file
  1. using System.Linq;
  2. using System.Linq.Expressions;
  3. using Microsoft.TestCommon;
  4. using Xunit;
  5. using Xunit.Extensions;
  6. using Assert = Microsoft.TestCommon.AssertEx;
  7. namespace System.Web.Http.Query
  8. {
  9. public class ODataQueryDeserializerTests
  10. {
  11. [Fact]
  12. public void SimpleMultipartQuery()
  13. {
  14. VerifyQueryDeserialization(
  15. "$filter=ProductName eq 'Doritos'&$orderby=UnitPrice&$top=100",
  16. "Where(Param_0 => (Param_0.ProductName == \"Doritos\")).OrderBy(Param_1 => Param_1.UnitPrice).Take(100)");
  17. }
  18. #region Ordering
  19. [Fact]
  20. public void OrderBy()
  21. {
  22. VerifyQueryDeserialization(
  23. "$orderby=UnitPrice",
  24. "OrderBy(Param_0 => Param_0.UnitPrice)");
  25. }
  26. [Fact]
  27. public void OrderByAscending()
  28. {
  29. VerifyQueryDeserialization(
  30. "$orderby=UnitPrice asc",
  31. "OrderBy(Param_0 => Param_0.UnitPrice)");
  32. }
  33. [Fact]
  34. public void OrderByDescending()
  35. {
  36. VerifyQueryDeserialization(
  37. "$orderby=UnitPrice desc",
  38. "OrderByDescending(Param_0 => Param_0.UnitPrice)");
  39. }
  40. [Fact]
  41. public void OrderByAscendingThenDesscending()
  42. {
  43. VerifyQueryDeserialization(
  44. "$orderby=UnitPrice desc, ProductName asc",
  45. "OrderByDescending(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)");
  46. }
  47. [Theory]
  48. [InlineData("UnitPrice desc, ProductName asc", "OrderByDescending(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)")]
  49. [InlineData("UnitPrice desc, ProductName desc", "OrderByDescending(Param_0 => Param_0.UnitPrice).ThenByDescending(Param_0 => Param_0.ProductName)")]
  50. [InlineData("UnitPrice , ProductName ", "OrderBy(Param_0 => Param_0.UnitPrice).ThenBy(Param_0 => Param_0.ProductName)")]
  51. [InlineData("Discontinued, UnitsOnOrder, DiscontinuedDate desc", "OrderBy(Param_0 => Param_0.Discontinued).ThenBy(Param_0 => Param_0.UnitsOnOrder).ThenByDescending(Param_0 => Param_0.DiscontinuedDate)")]
  52. public void MultipleOrderBy(string clause, string expressionResult)
  53. {
  54. VerifyQueryDeserialization(
  55. "$orderby=" + clause,
  56. expressionResult);
  57. }
  58. #endregion
  59. #region Inequalities
  60. [Fact]
  61. public void EqualityOperator()
  62. {
  63. VerifyQueryDeserialization(
  64. "$filter=ProductName eq 'Doritos'",
  65. "Where(Param_0 => (Param_0.ProductName == \"Doritos\"))");
  66. }
  67. [Fact]
  68. public void NotEqualOperator()
  69. {
  70. VerifyQueryDeserialization(
  71. "$filter=ProductName ne 'Doritos'",
  72. "Where(Param_0 => (Param_0.ProductName != \"Doritos\"))");
  73. }
  74. [Fact]
  75. public void GreaterThanOperator()
  76. {
  77. VerifyQueryDeserialization(
  78. "$filter=UnitPrice gt 5.00",
  79. "Where(Param_0 => (Param_0.UnitPrice > 5.00))");
  80. }
  81. [Fact]
  82. public void GreaterThanEqualOperator()
  83. {
  84. VerifyQueryDeserialization(
  85. "$filter=UnitPrice ge 5.00",
  86. "Where(Param_0 => (Param_0.UnitPrice >= 5.00))");
  87. }
  88. [Fact]
  89. public void LessThanOperator()
  90. {
  91. VerifyQueryDeserialization(
  92. "$filter=UnitPrice lt 5.00",
  93. "Where(Param_0 => (Param_0.UnitPrice < 5.00))");
  94. }
  95. [Fact]
  96. public void LessThanOrEqualOperator()
  97. {
  98. VerifyQueryDeserialization(
  99. "$filter=UnitPrice le 5.00",
  100. "Where(Param_0 => (Param_0.UnitPrice <= 5.00))");
  101. }
  102. [Fact]
  103. public void NegativeNumbers()
  104. {
  105. VerifyQueryDeserialization(
  106. "$filter=UnitPrice le -5.00",
  107. "Where(Param_0 => (Param_0.UnitPrice <= -5.00))");
  108. }
  109. [Theory]
  110. [InlineData("DateTimeProp eq datetime'2000-12-12T12:00'", "Where(Param_0 => (Param_0.DateTimeProp == 12/12/2000 12:00:00 PM))")]
  111. [InlineData("DateTimeOffsetProp eq datetimeoffset'2002-10-10T17:00:00Z'", "Where(Param_0 => (Param_0.DateTimeOffsetProp == 10/10/2002 5:00:00 PM +00:00))")]
  112. [InlineData("DateTimeOffsetProp eq DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp == Param_0.DateTimeOffsetProp))")]
  113. [InlineData("DateTimeOffsetProp ne DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp != Param_0.DateTimeOffsetProp))")]
  114. [InlineData("DateTimeOffsetProp ge DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp >= Param_0.DateTimeOffsetProp))")]
  115. [InlineData("DateTimeOffsetProp le DateTimeOffsetProp", "Where(Param_0 => (Param_0.DateTimeOffsetProp <= Param_0.DateTimeOffsetProp))")]
  116. public void DateInEqualities(string clause, string expectedExpression)
  117. {
  118. VerifyQueryDeserialization<DataTypes>(
  119. "$filter=" + clause,
  120. expectedExpression);
  121. }
  122. #endregion
  123. #region Logical Operators
  124. [Fact]
  125. public void OrOperator()
  126. {
  127. VerifyQueryDeserialization(
  128. "$filter=UnitPrice eq 5.00 or UnitPrice eq 10.00",
  129. "Where(Param_0 => ((Param_0.UnitPrice == 5.00) OrElse (Param_0.UnitPrice == 10.00)))");
  130. }
  131. [Fact]
  132. public void AndOperator()
  133. {
  134. VerifyQueryDeserialization(
  135. "$filter=UnitPrice eq 5.00 and UnitPrice eq 10.00",
  136. "Where(Param_0 => ((Param_0.UnitPrice == 5.00) AndAlso (Param_0.UnitPrice == 10.00)))");
  137. }
  138. [Fact]
  139. public void Negation()
  140. {
  141. VerifyQueryDeserialization(
  142. "$filter=not (UnitPrice eq 5.00)",
  143. "Where(Param_0 => Not((Param_0.UnitPrice == 5.00)))");
  144. }
  145. [Fact]
  146. public void BoolNegation()
  147. {
  148. VerifyQueryDeserialization(
  149. "$filter=not Discontinued",
  150. "Where(Param_0 => Not(Param_0.Discontinued))");
  151. }
  152. [Fact]
  153. public void NestedNegation()
  154. {
  155. VerifyQueryDeserialization(
  156. "$filter=not (not(not (Discontinued)))",
  157. "Where(Param_0 => Not(Not(Not(Param_0.Discontinued))))");
  158. }
  159. #endregion
  160. #region Arithmetic Operators
  161. [Fact]
  162. public void Subtraction()
  163. {
  164. VerifyQueryDeserialization(
  165. "$filter=UnitPrice sub 1.00 lt 5.00",
  166. "Where(Param_0 => ((Param_0.UnitPrice - 1.00) < 5.00))");
  167. }
  168. [Fact]
  169. public void Addition()
  170. {
  171. VerifyQueryDeserialization(
  172. "$filter=UnitPrice add 1.00 lt 5.00",
  173. "Where(Param_0 => ((Param_0.UnitPrice + 1.00) < 5.00))");
  174. }
  175. [Fact]
  176. public void Multiplication()
  177. {
  178. VerifyQueryDeserialization(
  179. "$filter=UnitPrice mul 1.00 lt 5.00",
  180. "Where(Param_0 => ((Param_0.UnitPrice * 1.00) < 5.00))");
  181. }
  182. [Fact]
  183. public void Division()
  184. {
  185. VerifyQueryDeserialization(
  186. "$filter=UnitPrice div 1.00 lt 5.00",
  187. "Where(Param_0 => ((Param_0.UnitPrice / 1.00) < 5.00))");
  188. }
  189. [Fact]
  190. public void Modulo()
  191. {
  192. VerifyQueryDeserialization(
  193. "$filter=UnitPrice mod 1.00 lt 5.00",
  194. "Where(Param_0 => ((Param_0.UnitPrice % 1.00) < 5.00))");
  195. }
  196. #endregion
  197. [Fact]
  198. public void Grouping()
  199. {
  200. VerifyQueryDeserialization(
  201. "$filter=((ProductName ne 'Doritos') or (UnitPrice lt 5.00))",
  202. "Where(Param_0 => ((Param_0.ProductName != \"Doritos\") OrElse (Param_0.UnitPrice < 5.00)))");
  203. }
  204. [Fact]
  205. public void MemberExpressions()
  206. {
  207. VerifyQueryDeserialization(
  208. "$filter=Category/CategoryName eq 'Snacks'",
  209. "Where(Param_0 => (Param_0.Category.CategoryName == \"Snacks\"))");
  210. }
  211. #region String Functions
  212. [Fact]
  213. public void StringSubstringOf()
  214. {
  215. // In OData, the order of parameters is actually reversed in the resulting
  216. // string.Contains expression
  217. VerifyQueryDeserialization(
  218. "$filter=substringof('Abc', ProductName) eq true",
  219. "Where(Param_0 => (Param_0.ProductName.Contains(\"Abc\") == True))");
  220. VerifyQueryDeserialization(
  221. "$filter=substringof(ProductName, 'Abc') eq true",
  222. "Where(Param_0 => (\"Abc\".Contains(Param_0.ProductName) == True))");
  223. }
  224. [Fact]
  225. public void StringStartsWith()
  226. {
  227. VerifyQueryDeserialization(
  228. "$filter=startswith(ProductName, 'Abc') eq true",
  229. "Where(Param_0 => (Param_0.ProductName.StartsWith(\"Abc\") == True))");
  230. }
  231. [Fact]
  232. public void StringEndsWith()
  233. {
  234. VerifyQueryDeserialization(
  235. "$filter=endswith(ProductName, 'Abc') eq true",
  236. "Where(Param_0 => (Param_0.ProductName.EndsWith(\"Abc\") == True))");
  237. }
  238. [Fact]
  239. public void StringLength()
  240. {
  241. VerifyQueryDeserialization(
  242. "$filter=length(ProductName) gt 0",
  243. "Where(Param_0 => (Param_0.ProductName.Length > 0))");
  244. }
  245. [Fact]
  246. public void StringIndexOf()
  247. {
  248. VerifyQueryDeserialization(
  249. "$filter=indexof(ProductName, 'Abc') eq 5",
  250. "Where(Param_0 => (Param_0.ProductName.IndexOf(\"Abc\") == 5))");
  251. }
  252. [Fact]
  253. public void StringReplace()
  254. {
  255. VerifyQueryDeserialization(
  256. "$filter=replace(ProductName, 'Abc', 'Def') eq 'FooDef'",
  257. "Where(Param_0 => (Param_0.ProductName.Replace(\"Abc\", \"Def\") == \"FooDef\"))");
  258. }
  259. [Fact]
  260. public void StringSubstring()
  261. {
  262. VerifyQueryDeserialization(
  263. "$filter=substring(ProductName, 3) eq 'uctName'",
  264. "Where(Param_0 => (Param_0.ProductName.Substring(3) == \"uctName\"))");
  265. VerifyQueryDeserialization(
  266. "$filter=substring(ProductName, 3, 4) eq 'uctN'",
  267. "Where(Param_0 => (Param_0.ProductName.Substring(3, 4) == \"uctN\"))");
  268. }
  269. [Fact]
  270. public void StringToLower()
  271. {
  272. VerifyQueryDeserialization(
  273. "$filter=tolower(ProductName) eq 'tasty treats'",
  274. "Where(Param_0 => (Param_0.ProductName.ToLower() == \"tasty treats\"))");
  275. }
  276. [Fact]
  277. public void StringToUpper()
  278. {
  279. VerifyQueryDeserialization(
  280. "$filter=toupper(ProductName) eq 'TASTY TREATS'",
  281. "Where(Param_0 => (Param_0.ProductName.ToUpper() == \"TASTY TREATS\"))");
  282. }
  283. [Fact]
  284. public void StringTrim()
  285. {
  286. VerifyQueryDeserialization(
  287. "$filter=trim(ProductName) eq 'Tasty Treats'",
  288. "Where(Param_0 => (Param_0.ProductName.Trim() == \"Tasty Treats\"))");
  289. }
  290. [Fact]
  291. public void StringConcat()
  292. {
  293. VerifyQueryDeserialization(
  294. "$filter=concat('Foo', 'Bar') eq 'FooBar'",
  295. "Where(Param_0 => (Concat(\"Foo\", \"Bar\") == \"FooBar\"))");
  296. }
  297. #endregion
  298. #region Date Functions
  299. [Fact]
  300. public void DateDay()
  301. {
  302. VerifyQueryDeserialization(
  303. "$filter=day(DiscontinuedDate) eq 8",
  304. "Where(Param_0 => (Param_0.DiscontinuedDate.Day == 8))");
  305. }
  306. [Fact]
  307. public void DateMonth()
  308. {
  309. VerifyQueryDeserialization(
  310. "$filter=month(DiscontinuedDate) eq 8",
  311. "Where(Param_0 => (Param_0.DiscontinuedDate.Month == 8))");
  312. }
  313. [Fact]
  314. public void DateYear()
  315. {
  316. VerifyQueryDeserialization(
  317. "$filter=year(DiscontinuedDate) eq 1974",
  318. "Where(Param_0 => (Param_0.DiscontinuedDate.Year == 1974))");
  319. }
  320. [Fact]
  321. public void DateHour()
  322. {
  323. VerifyQueryDeserialization("$filter=hour(DiscontinuedDate) eq 8",
  324. "Where(Param_0 => (Param_0.DiscontinuedDate.Hour == 8))");
  325. }
  326. [Fact]
  327. public void DateMinute()
  328. {
  329. VerifyQueryDeserialization(
  330. "$filter=minute(DiscontinuedDate) eq 12",
  331. "Where(Param_0 => (Param_0.DiscontinuedDate.Minute == 12))");
  332. }
  333. [Fact]
  334. public void DateSecond()
  335. {
  336. VerifyQueryDeserialization(
  337. "$filter=second(DiscontinuedDate) eq 33",
  338. "Where(Param_0 => (Param_0.DiscontinuedDate.Second == 33))");
  339. }
  340. #endregion
  341. #region Math Functions
  342. [Fact]
  343. public void MathRound()
  344. {
  345. VerifyQueryDeserialization(
  346. "$filter=round(UnitPrice) gt 5.00",
  347. "Where(Param_0 => (Round(Param_0.UnitPrice) > 5.00))");
  348. }
  349. [Fact]
  350. public void MathFloor()
  351. {
  352. VerifyQueryDeserialization(
  353. "$filter=floor(UnitPrice) eq 5",
  354. "Where(Param_0 => (Floor(Param_0.UnitPrice) == 5))");
  355. }
  356. [Fact]
  357. public void MathCeiling()
  358. {
  359. VerifyQueryDeserialization(
  360. "$filter=ceiling(UnitPrice) eq 5",
  361. "Where(Param_0 => (Ceiling(Param_0.UnitPrice) == 5))");
  362. }
  363. #endregion
  364. #region Data Types
  365. [Fact]
  366. public void GuidExpression()
  367. {
  368. VerifyQueryDeserialization<DataTypes>(
  369. "$filter=GuidProp eq guid'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
  370. "Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
  371. // verify case insensitivity
  372. VerifyQueryDeserialization<DataTypes>(
  373. "$filter=GuidProp eq GuiD'0EFDAECF-A9F0-42F3-A384-1295917AF95E'",
  374. "Where(Param_0 => (Param_0.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e))");
  375. }
  376. [Fact]
  377. public void DateTimeExpression()
  378. {
  379. VerifyQueryDeserialization<DataTypes>(
  380. "$filter=DateTimeProp lt datetime'2000-12-12T12:00'",
  381. "Where(Param_0 => (Param_0.DateTimeProp < 12/12/2000 12:00:00 PM))");
  382. }
  383. [Fact]
  384. public void DateTimeOffsetExpression()
  385. {
  386. VerifyQueryDeserialization<DataTypes>(
  387. "$filter=DateTimeOffsetProp ge datetimeoffset'2002-10-10T17:00:00Z'",
  388. "Where(Param_0 => (Param_0.DateTimeOffsetProp >= 10/10/2002 5:00:00 PM +00:00))");
  389. }
  390. [Fact]
  391. public void TimeExpression()
  392. {
  393. VerifyQueryDeserialization<DataTypes>(
  394. "$filter=TimeSpanProp ge time'13:20:00'",
  395. "Where(Param_0 => (Param_0.TimeSpanProp >= 13:20:00))");
  396. // verify parse error for invalid literal format
  397. Assert.Throws<ParseException>(delegate
  398. {
  399. VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'invalid'", String.Empty);
  400. },
  401. "Parse error in $filter. String was not recognized as a valid TimeSpan. (at index 20)");
  402. }
  403. [Fact]
  404. public void BinaryExpression()
  405. {
  406. string binary = "23ABFF";
  407. byte[] bytes = new byte[] {
  408. byte.Parse("23", Globalization.NumberStyles.HexNumber),
  409. byte.Parse("AB", Globalization.NumberStyles.HexNumber),
  410. byte.Parse("FF", Globalization.NumberStyles.HexNumber)
  411. };
  412. VerifyQueryDeserialization<DataTypes>(
  413. String.Format("$filter=ByteArrayProp eq binary'{0}'", binary),
  414. "Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
  415. q =>
  416. {
  417. // verify that the binary data was deserialized into a constant expression of type byte[]
  418. LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
  419. BinaryExpression bex = (BinaryExpression)lex.Body;
  420. byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
  421. Assert.True(actualBytes.SequenceEqual(bytes));
  422. });
  423. // test alternate 'X' syntax
  424. VerifyQueryDeserialization<DataTypes>(
  425. String.Format("$filter=ByteArrayProp eq X'{0}'", binary),
  426. "Where(Param_0 => (Param_0.ByteArrayProp == value(System.Byte[])))",
  427. q =>
  428. {
  429. // verify that the binary data was deserialized into a constant expression of type byte[]
  430. LambdaExpression lex = (LambdaExpression)((UnaryExpression)((MethodCallExpression)q.Expression).Arguments[1]).Operand;
  431. BinaryExpression bex = (BinaryExpression)lex.Body;
  432. byte[] actualBytes = (byte[])((ConstantExpression)bex.Right).Value;
  433. Assert.True(actualBytes.SequenceEqual(bytes));
  434. });
  435. // verify parse error for invalid literal format
  436. Assert.Throws<ParseException>(delegate
  437. {
  438. VerifyQueryDeserialization<DataTypes>(
  439. String.Format("$filter=ByteArrayProp eq binary'{0}'", "WXYZ"), String.Empty);
  440. },
  441. "Parse error in $filter. Input string was not in a correct format. (at index 23)");
  442. // verify parse error for invalid hex literal (odd hex strings are not supported)
  443. Assert.Throws<ParseException>(delegate
  444. {
  445. VerifyQueryDeserialization<DataTypes>(
  446. String.Format("$filter=ByteArrayProp eq binary'23A'", "XYZ"), String.Empty);
  447. },
  448. "Parse error in $filter. Invalid hexadecimal literal. (at index 23)");
  449. }
  450. [Fact]
  451. public void IntegerLiteralSuffix()
  452. {
  453. // long L
  454. VerifyQueryDeserialization<DataTypes>(
  455. "$filter=LongProp lt 987654321L and LongProp gt 123456789l",
  456. "Where(Param_0 => ((Param_0.LongProp < 987654321) AndAlso (Param_0.LongProp > 123456789)))");
  457. VerifyQueryDeserialization<DataTypes>(
  458. "$filter=LongProp lt -987654321L and LongProp gt -123456789l",
  459. "Where(Param_0 => ((Param_0.LongProp < -987654321) AndAlso (Param_0.LongProp > -123456789)))");
  460. }
  461. [Fact]
  462. public void RealLiteralSuffixes()
  463. {
  464. // Float F
  465. VerifyQueryDeserialization<DataTypes>(
  466. "$filter=FloatProp lt 4321.56F and FloatProp gt 1234.56f",
  467. "Where(Param_0 => ((Param_0.FloatProp < 4321.56) AndAlso (Param_0.FloatProp > 1234.56)))");
  468. // Decimal M
  469. VerifyQueryDeserialization<DataTypes>(
  470. "$filter=DecimalProp lt 4321.56M and DecimalProp gt 1234.56m",
  471. "Where(Param_0 => ((Param_0.DecimalProp < 4321.56) AndAlso (Param_0.DecimalProp > 1234.56)))");
  472. }
  473. [Theory]
  474. [InlineData("'hello,world'", "hello,world")]
  475. [InlineData("'''hello,world'", "'hello,world")]
  476. [InlineData("'hello,world'''", "hello,world'")]
  477. [InlineData("'hello,''wor''ld'", "hello,'wor'ld")]
  478. [InlineData("'hello,''''''world'", "hello,'''world")]
  479. [InlineData("'\"hello,world\"'", "\"hello,world\"")]
  480. [InlineData("'\"hello,world'", "\"hello,world")]
  481. [InlineData("'hello,world\"'", "hello,world\"")]
  482. [InlineData("'hello,\"world'", "hello,\"world")]
  483. [InlineData("'México D.F.'", "México D.F.")]
  484. public void StringLiterals(string literal, string expected)
  485. {
  486. VerifyQueryDeserialization<Product>(
  487. "$filter=ProductName eq " + literal,
  488. String.Format("Where(Param_0 => (Param_0.ProductName == \"{0}\"))", expected));
  489. }
  490. #endregion
  491. #region Negative tests
  492. [Theory]
  493. [InlineData('+')]
  494. [InlineData('*')]
  495. [InlineData('%')]
  496. [InlineData('[')]
  497. [InlineData(']')]
  498. public void InvalidCharactersInQuery_TokenizerFails(char ch)
  499. {
  500. Assert.Throws<ParseException>(delegate
  501. {
  502. string filter = String.Format("2 {0} 3", ch);
  503. VerifyQueryDeserialization<DataTypes>("$filter=" + Uri.EscapeDataString(filter), String.Empty);
  504. },
  505. String.Format("Parse error in $filter. Syntax error '{0}' (at index 2)", ch));
  506. }
  507. [Fact]
  508. public void InvalidTypeCreationExpression()
  509. {
  510. // underminated string literal
  511. Assert.Throws<ParseException>(delegate
  512. {
  513. VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time'13:20:00", String.Empty);
  514. },
  515. "Parse error in $filter. Unterminated string literal (at index 29)");
  516. // use of parens rather than quotes
  517. Assert.Throws<ParseException>(delegate
  518. {
  519. VerifyQueryDeserialization<DataTypes>("$filter=TimeSpanProp ge time(13:20:00)", String.Empty);
  520. },
  521. "Parse error in $filter. Invalid 'time' type creation expression. (at index 16)");
  522. // verify the exception returned when type expression that isn't
  523. // one of the supported keyword types is used. In this case it falls
  524. // through as a member expression
  525. Assert.Throws<ParseException>(delegate
  526. {
  527. VerifyQueryDeserialization("$filter=math'123' eq true", String.Empty);
  528. },
  529. "Parse error in $filter. No property or field 'math' exists in type 'Product' (at index 0)");
  530. }
  531. [Fact]
  532. public void InvalidMethodCall()
  533. {
  534. // incorrect casing of supported method
  535. Assert.Throws<ParseException>(delegate
  536. {
  537. VerifyQueryDeserialization("$filter=Startswith(ProductName, 'Abc') eq true", String.Empty);
  538. },
  539. "Parse error in $filter. Unknown identifier 'Startswith' (at index 0)");
  540. // attempt to access a method defined on the entity type
  541. Assert.Throws<ParseException>(delegate
  542. {
  543. VerifyQueryDeserialization<DataTypes>("$filter=Inaccessable() eq \"Bar\"", String.Empty);
  544. },
  545. "Parse error in $filter. Unknown identifier 'Inaccessable' (at index 0)");
  546. // verify that Type methods like string.PadLeft, etc. are not supported.
  547. Assert.Throws<ParseException>(delegate
  548. {
  549. VerifyQueryDeserialization("$filter=ProductName/PadLeft(100000000000000000000) eq \"Foo\"", String.Empty);
  550. },
  551. "Parse error in $filter. Unknown identifier 'PadLeft' (at index 12)");
  552. }
  553. [Fact]
  554. public void InvalidQueryParameterToTop()
  555. {
  556. Assert.Throws<ParseException>(
  557. () => VerifyQueryDeserialization("$top=-42", String.Empty),
  558. "The OData query parameter '$top' has an invalid value. The value should be a positive integer. The provided value was '-42'");
  559. }
  560. [Fact]
  561. public void InvalidQueryParameterToSkip()
  562. {
  563. Assert.Throws<ParseException>(
  564. () => VerifyQueryDeserialization("$skip=-42", String.Empty),
  565. "The OData query parameter '$skip' has an invalid value. The value should be a positive integer. The provided value was '-42'");
  566. }
  567. [Fact]
  568. public void InvalidProperty_NotExists()
  569. {
  570. Assert.Throws<ParseException>(
  571. () => VerifyQueryDeserialization("$filter=length mod n(ProductName) eq 12", String.Empty),
  572. "Parse error in $filter. No property or field 'length' exists in type 'Product' (at index 0)");
  573. }
  574. [Fact]
  575. public void InvalidFunctionCall_EmptyArguments()
  576. {
  577. Assert.Throws<ParseException>(
  578. () => VerifyQueryDeserialization("$filter=length() eq 12", String.Empty),
  579. "Parse error in $filter. No applicable method 'Length' exists in type 'System.String' (at index 0)");
  580. }
  581. [Theory]
  582. [InlineData("(2 add 3 eq 2", 13)]
  583. [InlineData("(2 add (3) eq 2", 15)]
  584. [InlineData("(((( 2 eq 2", 11)]
  585. public void Missing_Parantheses(string clause, int index)
  586. {
  587. Assert.Throws<ParseException>(
  588. () => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
  589. String.Format("Parse error in $filter. ')' or operator expected (at index {0})", index));
  590. }
  591. [Theory]
  592. [InlineData("'hello,world", 12)]
  593. [InlineData("'''hello,world", 14)]
  594. [InlineData("'hello,world''", 14)]
  595. [InlineData("'hello,''wor''ld", 16)]
  596. public void UnterminatedStringLiterals(string clause, int index)
  597. {
  598. Assert.Throws<ParseException>(
  599. () => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
  600. String.Format("Parse error in $filter. Unterminated string literal (at index {0})", index));
  601. }
  602. [Theory]
  603. [InlineData("\"hello,world\"", 0)]
  604. public void InvalidStringLiterals(string clause, int index)
  605. {
  606. Assert.Throws<ParseException>(
  607. () => VerifyQueryDeserialization("$filter=" + clause, String.Empty),
  608. String.Format("Parse error in $filter. Syntax error '\"' (at index {0})", index));
  609. }
  610. #endregion
  611. [Fact(DisplayName = "ODataQueryDeserializer is internal.")]
  612. public void TypeIsCorrect()
  613. {
  614. Assert.Type.HasProperties(typeof(ODataQueryDeserializer), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsClass);
  615. }
  616. /// <summary>
  617. /// Call the query deserializer and verify the results
  618. /// </summary>
  619. /// <param name="queryString">The URL query string to deserialize (e.g. $filter=ProductName eq 'Doritos')</param>
  620. /// <param name="expectedResult">The Expression.ToString() representation of the expected result (e.g. Where(Param_0 => (Param_0.ProductName == \"Doritos\"))</param>
  621. private void VerifyQueryDeserialization(string queryString, string expectedResult)
  622. {
  623. VerifyQueryDeserialization<Product>(queryString, expectedResult, null);
  624. }
  625. private void VerifyQueryDeserialization<T>(string queryString, string expectedResult)
  626. {
  627. VerifyQueryDeserialization<T>(queryString, expectedResult, null);
  628. }
  629. private void VerifyQueryDeserialization<T>(string queryString, string expectedResult, Action<IQueryable<T>> verify)
  630. {
  631. string uri = "http://myhost/odata.svc/Get?" + queryString;
  632. IQueryable<T> baseQuery = new T[0].AsQueryable();
  633. IQueryable<T> resultQuery = (IQueryable<T>)ODataQueryDeserializer.Deserialize(baseQuery, new Uri(uri));
  634. VerifyExpression(resultQuery, expectedResult);
  635. if (verify != null)
  636. {
  637. verify(resultQuery);
  638. }
  639. QueryValidator.Instance.Validate(resultQuery);
  640. }
  641. private void VerifyExpression(IQueryable query, string expectedExpression)
  642. {
  643. // strip off the beginning part of the expression to get to the first
  644. // actual query operator
  645. string resultExpression = query.Expression.ToString();
  646. int startIdx = (query.ElementType.FullName + "[]").Length + 1;
  647. resultExpression = resultExpression.Substring(startIdx);
  648. Assert.True(resultExpression == expectedExpression,
  649. String.Format("Expected expression '{0}' but the deserializer produced '{1}'", expectedExpression, resultExpression));
  650. }
  651. }
  652. }