UsePathBaseExtensionsTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using Microsoft.AspNetCore.Http;
  4. using Microsoft.AspNetCore.Http.Features;
  5. using Microsoft.AspNetCore.TestHost;
  6. namespace Microsoft.AspNetCore.Builder.Extensions;
  7. public class UsePathBaseExtensionsTests
  8. {
  9. [Theory]
  10. [InlineData(null)]
  11. [InlineData("")]
  12. [InlineData("/")]
  13. public void EmptyOrNullPathBase_DoNotAddMiddleware(string? pathBase)
  14. {
  15. // Arrange
  16. var useCalled = false;
  17. var builder = new ApplicationBuilderWrapper(CreateBuilder(), () => useCalled = true)
  18. .UsePathBase(pathBase);
  19. // Act
  20. builder.Build();
  21. // Assert
  22. Assert.False(useCalled);
  23. }
  24. private class ApplicationBuilderWrapper : IApplicationBuilder
  25. {
  26. private readonly IApplicationBuilder _wrappedBuilder;
  27. private readonly Action _useCallback;
  28. public ApplicationBuilderWrapper(IApplicationBuilder applicationBuilder, Action useCallback)
  29. {
  30. _wrappedBuilder = applicationBuilder;
  31. _useCallback = useCallback;
  32. }
  33. public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  34. {
  35. _useCallback();
  36. return _wrappedBuilder.Use(middleware);
  37. }
  38. public IServiceProvider ApplicationServices
  39. {
  40. get { return _wrappedBuilder.ApplicationServices; }
  41. set { _wrappedBuilder.ApplicationServices = value; }
  42. }
  43. public IDictionary<string, object?> Properties => _wrappedBuilder.Properties;
  44. public IFeatureCollection ServerFeatures => _wrappedBuilder.ServerFeatures;
  45. public RequestDelegate Build() => _wrappedBuilder.Build();
  46. public IApplicationBuilder New() => _wrappedBuilder.New();
  47. }
  48. [Theory]
  49. [InlineData("/base", "", "/base", "/base", "")]
  50. [InlineData("/base", "", "/base/", "/base", "/")]
  51. [InlineData("/base", "", "/base/something", "/base", "/something")]
  52. [InlineData("/base", "", "/base/something/", "/base", "/something/")]
  53. [InlineData("/base/more", "", "/base/more", "/base/more", "")]
  54. [InlineData("/base/more", "", "/base/more/something", "/base/more", "/something")]
  55. [InlineData("/base/more", "", "/base/more/something/", "/base/more", "/something/")]
  56. [InlineData("/base", "/oldbase", "/base", "/oldbase/base", "")]
  57. [InlineData("/base", "/oldbase", "/base/", "/oldbase/base", "/")]
  58. [InlineData("/base", "/oldbase", "/base/something", "/oldbase/base", "/something")]
  59. [InlineData("/base", "/oldbase", "/base/something/", "/oldbase/base", "/something/")]
  60. [InlineData("/base/more", "/oldbase", "/base/more", "/oldbase/base/more", "")]
  61. [InlineData("/base/more", "/oldbase", "/base/more/something", "/oldbase/base/more", "/something")]
  62. [InlineData("/base/more", "/oldbase", "/base/more/something/", "/oldbase/base/more", "/something/")]
  63. public Task RequestPathBaseContainingPathBase_IsSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  64. {
  65. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  66. }
  67. [Theory]
  68. [InlineData("/base", "", "/something", "", "/something")]
  69. [InlineData("/base", "", "/baseandsomething", "", "/baseandsomething")]
  70. [InlineData("/base", "", "/ba", "", "/ba")]
  71. [InlineData("/base", "", "/ba/se", "", "/ba/se")]
  72. [InlineData("/base", "/oldbase", "/something", "/oldbase", "/something")]
  73. [InlineData("/base", "/oldbase", "/baseandsomething", "/oldbase", "/baseandsomething")]
  74. [InlineData("/base", "/oldbase", "/ba", "/oldbase", "/ba")]
  75. [InlineData("/base", "/oldbase", "/ba/se", "/oldbase", "/ba/se")]
  76. public Task RequestPathBaseNotContainingPathBase_IsNotSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  77. {
  78. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  79. }
  80. [Theory]
  81. [InlineData("", "", "/", "", "/")]
  82. [InlineData("/", "", "/", "", "/")]
  83. [InlineData("/base", "", "/base/", "/base", "/")]
  84. [InlineData("/base/", "", "/base", "/base", "")]
  85. [InlineData("/base/", "", "/base/", "/base", "/")]
  86. [InlineData("", "/oldbase", "/", "/oldbase", "/")]
  87. [InlineData("/", "/oldbase", "/", "/oldbase", "/")]
  88. [InlineData("/base", "/oldbase", "/base/", "/oldbase/base", "/")]
  89. [InlineData("/base/", "/oldbase", "/base", "/oldbase/base", "")]
  90. [InlineData("/base/", "/oldbase", "/base/", "/oldbase/base", "/")]
  91. public Task PathBaseNeverEndsWithSlash(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  92. {
  93. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  94. }
  95. [Theory]
  96. [InlineData("/base", "", "/Base/Something", "/Base", "/Something")]
  97. [InlineData("/base", "/OldBase", "/Base/Something", "/OldBase/Base", "/Something")]
  98. public Task PathBaseAndPathPreserveRequestCasing(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  99. {
  100. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  101. }
  102. [Theory]
  103. [InlineData("/b♫se", "", "/b♫se/something", "/b♫se", "/something")]
  104. [InlineData("/b♫se", "", "/B♫se/something", "/B♫se", "/something")]
  105. [InlineData("/b♫se", "", "/b♫se/Something", "/b♫se", "/Something")]
  106. [InlineData("/b♫se", "/oldb♫se", "/b♫se/something", "/oldb♫se/b♫se", "/something")]
  107. [InlineData("/b♫se", "/oldb♫se", "/b♫se/Something", "/oldb♫se/b♫se", "/Something")]
  108. [InlineData("/b♫se", "/oldb♫se", "/B♫se/something", "/oldb♫se/B♫se", "/something")]
  109. public Task PathBaseCanHaveUnicodeCharacters(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  110. {
  111. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  112. }
  113. [Theory]
  114. [InlineData("/b%42", "", "/b%42/something%42", "/b%42", "/something%42")]
  115. [InlineData("/b%42", "", "/B%42/something%42", "/B%42", "/something%42")]
  116. [InlineData("/b%42", "", "/b%42/Something%42", "/b%42", "/Something%42")]
  117. [InlineData("/b%42", "/oldb%42", "/b%42/something%42", "/oldb%42/b%42", "/something%42")]
  118. [InlineData("/b%42", "/oldb%42", "/b%42/Something%42", "/oldb%42/b%42", "/Something%42")]
  119. [InlineData("/b%42", "/oldb%42", "/B%42/something%42", "/oldb%42/B%42", "/something%42")]
  120. public Task PathBaseCanHavePercentCharacters(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  121. {
  122. return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
  123. }
  124. [Fact]
  125. public async Task PathBaseWorksAfterUseRoutingIfGlobalRouteBuilderUsed()
  126. {
  127. var builder = WebApplication.CreateBuilder();
  128. builder.WebHost.UseTestServer();
  129. await using var app = builder.Build();
  130. app.UseRouting();
  131. app.UsePathBase("/base");
  132. app.UseEndpoints(endpoints =>
  133. {
  134. endpoints.Map("/path", context => context.Response.WriteAsync("Response"));
  135. });
  136. await app.StartAsync();
  137. using var server = app.GetTestServer();
  138. var response = await server.CreateClient().GetStringAsync("/base/path");
  139. Assert.Equal("Response", response);
  140. }
  141. [Fact]
  142. public async Task PathBaseWorksBeforeUseRoutingIfGlobalRouteBuilderUsed()
  143. {
  144. var builder = WebApplication.CreateBuilder();
  145. builder.WebHost.UseTestServer();
  146. await using var app = builder.Build();
  147. app.UsePathBase("/base");
  148. app.UseRouting();
  149. app.MapGet("/path", context => context.Response.WriteAsync("Response"));
  150. await app.StartAsync();
  151. using var server = app.GetTestServer();
  152. var response = await server.CreateClient().GetStringAsync("/base/path");
  153. Assert.Equal("Response", response);
  154. }
  155. [Fact]
  156. public async Task PathBaseWorksWithoutUseRoutingWithWebApplication()
  157. {
  158. var builder = WebApplication.CreateBuilder();
  159. builder.WebHost.UseTestServer();
  160. await using var app = builder.Build();
  161. app.UsePathBase("/base");
  162. app.MapGet("/path", context => context.Response.WriteAsync("Response"));
  163. await app.StartAsync();
  164. using var server = app.GetTestServer();
  165. var response = await server.CreateClient().GetStringAsync("/base/path");
  166. Assert.Equal("Response", response);
  167. }
  168. private static async Task TestPathBase(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
  169. {
  170. HttpContext requestContext = CreateRequest(pathBase, requestPath);
  171. var builder = CreateBuilder()
  172. .UsePathBase(new PathString(registeredPathBase));
  173. builder.Run(context =>
  174. {
  175. context.Items["test.Path"] = context.Request.Path;
  176. context.Items["test.PathBase"] = context.Request.PathBase;
  177. return Task.FromResult(0);
  178. });
  179. await builder.Build().Invoke(requestContext);
  180. // Assert path and pathBase are split after middleware
  181. Assert.Equal(expectedPath, ((PathString?)requestContext.Items["test.Path"])!.Value.Value);
  182. Assert.Equal(expectedPathBase, ((PathString?)requestContext.Items["test.PathBase"])!.Value.Value);
  183. // Assert path and pathBase are reset after request
  184. Assert.Equal(pathBase, requestContext.Request.PathBase.Value);
  185. Assert.Equal(requestPath, requestContext.Request.Path.Value);
  186. }
  187. private static HttpContext CreateRequest(string pathBase, string requestPath)
  188. {
  189. HttpContext context = new DefaultHttpContext();
  190. context.Request.PathBase = new PathString(pathBase);
  191. context.Request.Path = new PathString(requestPath);
  192. return context;
  193. }
  194. private static ApplicationBuilder CreateBuilder()
  195. {
  196. return new ApplicationBuilder(new DummyServiceProvider());
  197. }
  198. private class DummyServiceProvider : IServiceProvider
  199. {
  200. private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
  201. public void AddService(Type type, object value) => _services[type] = value;
  202. public object? GetService(Type serviceType)
  203. {
  204. if (serviceType == typeof(IServiceProvider))
  205. {
  206. return this;
  207. }
  208. if (_services.TryGetValue(serviceType, out var value))
  209. {
  210. return value;
  211. }
  212. return null;
  213. }
  214. }
  215. }