LegacyTemplateParserTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using Xunit;
  7. namespace Microsoft.AspNetCore.Components.LegacyRouteMatching
  8. {
  9. public class LegacyTemplateParserTests
  10. {
  11. [Fact]
  12. public void Parse_SingleLiteral()
  13. {
  14. // Arrange
  15. var expected = new ExpectedTemplateBuilder().Literal("awesome");
  16. // Act
  17. var actual = LegacyTemplateParser.ParseTemplate("awesome");
  18. // Assert
  19. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  20. }
  21. [Fact]
  22. public void Parse_SingleParameter()
  23. {
  24. // Arrange
  25. var template = "{p}";
  26. var expected = new ExpectedTemplateBuilder().Parameter("p");
  27. // Act
  28. var actual = LegacyTemplateParser.ParseTemplate(template);
  29. // Assert
  30. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  31. }
  32. [Fact]
  33. public void Parse_MultipleLiterals()
  34. {
  35. // Arrange
  36. var template = "awesome/cool/super";
  37. var expected = new ExpectedTemplateBuilder().Literal("awesome").Literal("cool").Literal("super");
  38. // Act
  39. var actual = LegacyTemplateParser.ParseTemplate(template);
  40. // Assert
  41. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  42. }
  43. [Fact]
  44. public void Parse_MultipleParameters()
  45. {
  46. // Arrange
  47. var template = "{p1}/{p2}/{p3}";
  48. var expected = new ExpectedTemplateBuilder().Parameter("p1").Parameter("p2").Parameter("p3");
  49. // Act
  50. var actual = LegacyTemplateParser.ParseTemplate(template);
  51. // Assert
  52. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  53. }
  54. [Fact]
  55. public void Parse_MultipleOptionalParameters()
  56. {
  57. // Arrange
  58. var template = "{p1?}/{p2?}/{p3?}";
  59. var expected = new ExpectedTemplateBuilder().Parameter("p1?").Parameter("p2?").Parameter("p3?");
  60. // Act
  61. var actual = LegacyTemplateParser.ParseTemplate(template);
  62. // Assert
  63. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  64. }
  65. [Fact]
  66. public void Parse_SingleCatchAllParameter()
  67. {
  68. // Arrange
  69. var expected = new ExpectedTemplateBuilder().Parameter("p");
  70. // Act
  71. var actual = LegacyTemplateParser.ParseTemplate("{*p}");
  72. // Assert
  73. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  74. }
  75. [Fact]
  76. public void Parse_MixedLiteralAndCatchAllParameter()
  77. {
  78. // Arrange
  79. var expected = new ExpectedTemplateBuilder().Literal("awesome").Literal("wow").Parameter("p");
  80. // Act
  81. var actual = LegacyTemplateParser.ParseTemplate("awesome/wow/{*p}");
  82. // Assert
  83. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  84. }
  85. [Fact]
  86. public void Parse_MixedLiteralParameterAndCatchAllParameter()
  87. {
  88. // Arrange
  89. var expected = new ExpectedTemplateBuilder().Literal("awesome").Parameter("p1").Parameter("p2");
  90. // Act
  91. var actual = LegacyTemplateParser.ParseTemplate("awesome/{p1}/{*p2}");
  92. // Assert
  93. Assert.Equal(expected, actual, LegacyRouteTemplateTestComparer.Instance);
  94. }
  95. [Fact]
  96. public void InvalidTemplate_WithRepeatedParameter()
  97. {
  98. var ex = Assert.Throws<InvalidOperationException>(
  99. () => LegacyTemplateParser.ParseTemplate("{p1}/literal/{p1}"));
  100. var expectedMessage = "Invalid template '{p1}/literal/{p1}'. The parameter 'Microsoft.AspNetCore.Components.LegacyRouteMatching.LegacyTemplateSegment' appears multiple times.";
  101. Assert.Equal(expectedMessage, ex.Message);
  102. }
  103. [Theory]
  104. [InlineData("p}", "Invalid template 'p}'. Missing '{' in parameter segment 'p}'.")]
  105. [InlineData("{p", "Invalid template '{p'. Missing '}' in parameter segment '{p'.")]
  106. [InlineData("Literal/p}", "Invalid template 'Literal/p}'. Missing '{' in parameter segment 'p}'.")]
  107. [InlineData("Literal/{p", "Invalid template 'Literal/{p'. Missing '}' in parameter segment '{p'.")]
  108. [InlineData("p}/Literal", "Invalid template 'p}/Literal'. Missing '{' in parameter segment 'p}'.")]
  109. [InlineData("{p/Literal", "Invalid template '{p/Literal'. Missing '}' in parameter segment '{p'.")]
  110. [InlineData("Another/p}/Literal", "Invalid template 'Another/p}/Literal'. Missing '{' in parameter segment 'p}'.")]
  111. [InlineData("Another/{p/Literal", "Invalid template 'Another/{p/Literal'. Missing '}' in parameter segment '{p'.")]
  112. public void InvalidTemplate_WithMismatchedBraces(string template, string expectedMessage)
  113. {
  114. var ex = Assert.Throws<InvalidOperationException>(
  115. () => LegacyTemplateParser.ParseTemplate(template));
  116. Assert.Equal(expectedMessage, ex.Message);
  117. }
  118. [Theory]
  119. // * is only allowed at beginning for catch-all parameters
  120. [InlineData("{p*}", "Invalid template '{p*}'. The character '*' in parameter segment '{p*}' is not allowed.")]
  121. [InlineData("{{}", "Invalid template '{{}'. The character '{' in parameter segment '{{}' is not allowed.")]
  122. [InlineData("{}}", "Invalid template '{}}'. The character '}' in parameter segment '{}}' is not allowed.")]
  123. [InlineData("{=}", "Invalid template '{=}'. The character '=' in parameter segment '{=}' is not allowed.")]
  124. [InlineData("{.}", "Invalid template '{.}'. The character '.' in parameter segment '{.}' is not allowed.")]
  125. public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(string template, string expectedMessage)
  126. {
  127. // Act & Assert
  128. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate(template));
  129. Assert.Equal(expectedMessage, ex.Message);
  130. }
  131. [Fact]
  132. public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
  133. {
  134. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("{a}/{}/{z}"));
  135. var expectedMessage = "Invalid template '{a}/{}/{z}'. Empty parameter name in segment '{}' is not allowed.";
  136. Assert.Equal(expectedMessage, ex.Message);
  137. }
  138. [Fact]
  139. public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
  140. {
  141. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("{a}//{z}"));
  142. var expectedMessage = "Invalid template '{a}//{z}'. Empty segments are not allowed.";
  143. Assert.Equal(expectedMessage, ex.Message);
  144. }
  145. [Fact]
  146. public void InvalidTemplate_LiteralAfterOptionalParam()
  147. {
  148. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?}/test"));
  149. var expectedMessage = "Invalid template 'test/{a?}/test'. Non-optional parameters or literal routes cannot appear after optional parameters.";
  150. Assert.Equal(expectedMessage, ex.Message);
  151. }
  152. [Fact]
  153. public void InvalidTemplate_NonOptionalParamAfterOptionalParam()
  154. {
  155. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?}/{b}"));
  156. var expectedMessage = "Invalid template 'test/{a?}/{b}'. Non-optional parameters or literal routes cannot appear after optional parameters.";
  157. Assert.Equal(expectedMessage, ex.Message);
  158. }
  159. [Fact]
  160. public void InvalidTemplate_CatchAllParamWithMultipleAsterisks()
  161. {
  162. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{a}/{**b}"));
  163. var expectedMessage = "Invalid template '/test/{a}/{**b}'. A catch-all parameter may only have one '*' at the beginning of the segment.";
  164. Assert.Equal(expectedMessage, ex.Message);
  165. }
  166. [Fact]
  167. public void InvalidTemplate_CatchAllParamNotLast()
  168. {
  169. var ex = Assert.Throws<InvalidOperationException>(() => LegacyTemplateParser.ParseTemplate("/test/{*a}/{b}"));
  170. var expectedMessage = "Invalid template 'test/{*a}/{b}'. A catch-all parameter can only appear as the last segment of the route template.";
  171. Assert.Equal(expectedMessage, ex.Message);
  172. }
  173. [Fact]
  174. public void InvalidTemplate_BadOptionalCharacterPosition()
  175. {
  176. var ex = Assert.Throws<ArgumentException>(() => LegacyTemplateParser.ParseTemplate("/test/{a?bc}/{b}"));
  177. var expectedMessage = "Malformed parameter 'a?bc' in route '/test/{a?bc}/{b}'. '?' character can only appear at the end of parameter name.";
  178. Assert.Equal(expectedMessage, ex.Message);
  179. }
  180. private class ExpectedTemplateBuilder
  181. {
  182. public IList<LegacyTemplateSegment> Segments { get; set; } = new List<LegacyTemplateSegment>();
  183. public ExpectedTemplateBuilder Literal(string value)
  184. {
  185. Segments.Add(new LegacyTemplateSegment("testtemplate", value, isParameter: false));
  186. return this;
  187. }
  188. public ExpectedTemplateBuilder Parameter(string value)
  189. {
  190. Segments.Add(new LegacyTemplateSegment("testtemplate", value, isParameter: true));
  191. return this;
  192. }
  193. public LegacyRouteTemplate Build() => new LegacyRouteTemplate(string.Join('/', Segments), Segments.ToArray());
  194. public static implicit operator LegacyRouteTemplate(ExpectedTemplateBuilder builder) => builder.Build();
  195. }
  196. private class LegacyRouteTemplateTestComparer : IEqualityComparer<LegacyRouteTemplate>
  197. {
  198. public static LegacyRouteTemplateTestComparer Instance { get; } = new LegacyRouteTemplateTestComparer();
  199. public bool Equals(LegacyRouteTemplate x, LegacyRouteTemplate y)
  200. {
  201. if (x.Segments.Length != y.Segments.Length)
  202. {
  203. return false;
  204. }
  205. for (var i = 0; i < x.Segments.Length; i++)
  206. {
  207. var xSegment = x.Segments[i];
  208. var ySegment = y.Segments[i];
  209. if (xSegment.IsParameter != ySegment.IsParameter)
  210. {
  211. return false;
  212. }
  213. if (xSegment.IsOptional != ySegment.IsOptional)
  214. {
  215. return false;
  216. }
  217. if (!string.Equals(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase))
  218. {
  219. return false;
  220. }
  221. }
  222. return true;
  223. }
  224. public int GetHashCode(LegacyRouteTemplate obj) => 0;
  225. }
  226. }
  227. }