a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
310 lines
14 KiB
C#
310 lines
14 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Moq;
|
|
using Xunit;
|
|
using Xunit.Extensions;
|
|
using Assert = Microsoft.TestCommon.AssertEx;
|
|
|
|
namespace System.Web.WebPages.Test
|
|
{
|
|
public class WebPageRouteTest
|
|
{
|
|
private class HashyBuildManager : IVirtualPathFactory
|
|
{
|
|
private readonly HashSet<string> _existingFiles;
|
|
|
|
public HashyBuildManager(IEnumerable<string> validFilePaths)
|
|
{
|
|
_existingFiles = new HashSet<string>(validFilePaths, StringComparer.InvariantCultureIgnoreCase);
|
|
}
|
|
|
|
public bool Exists(string virtualPath)
|
|
{
|
|
return _existingFiles.Contains(virtualPath);
|
|
}
|
|
|
|
public object CreateInstance(string virtualPath)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
// Helper to test smarty route match, null match string is used for no expected match
|
|
private static void ConstraintTest(IEnumerable<string> validFiles, IEnumerable<string> supportedExt, string url, string match, string pathInfo, bool mobileDevice = false)
|
|
{
|
|
var objectFactory = new HashyBuildManager(validFiles);
|
|
var mockContext = new Mock<HttpContextBase>();
|
|
mockContext.Setup(context => context.Items).Returns(new Hashtable());
|
|
mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(mobileDevice);
|
|
mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
|
|
mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
|
|
var displayModeProvider = new DisplayModeProvider();
|
|
|
|
WebPageMatch smartyMatch = WebPageRoute.MatchRequest(url, supportedExt, objectFactory, mockContext.Object, displayModeProvider);
|
|
if (match != null)
|
|
{
|
|
Assert.NotNull(smartyMatch);
|
|
Assert.Equal(match, smartyMatch.MatchedPath);
|
|
Assert.Equal(pathInfo, smartyMatch.PathInfo);
|
|
}
|
|
else
|
|
{
|
|
Assert.Null(smartyMatch);
|
|
}
|
|
}
|
|
|
|
[Theory,
|
|
InlineData("1.1/2/3", "1.1/2/3.3", ""),
|
|
InlineData("1/2/3/4", "1.one", "2/3/4"),
|
|
InlineData("2/3/4", "2.two", "3/4"),
|
|
InlineData("one/two/3/4/5/6", "one/two/3/4.4", "5/6"),
|
|
InlineData("one/two/3/4/5/6/foo", "one/two/3/4.4", "5/6/foo"),
|
|
InlineData("one/two/3/4/5/6/foo.htm", null, null)]
|
|
public void MultipleExtensionsTest(string url, string match, string pathInfo)
|
|
{
|
|
string[] files = new[] { "~/1.one", "~/2.two", "~/1.1/2/3.3", "~/one/two/3/4.4", "~/one/two/3/4/5/6/foo.htm" };
|
|
string[] extensions = new[] { "aspx", "hao", "one", "two", "3", "4" };
|
|
|
|
ConstraintTest(files, extensions, url, match, pathInfo);
|
|
}
|
|
|
|
[Theory,
|
|
InlineData("1.1/2/3", "1.1/2/3.Mobile.3", ""),
|
|
InlineData("1/2/3/4", "1.Mobile.one", "2/3/4"),
|
|
InlineData("2/3/4", "2.Mobile.two", "3/4"),
|
|
InlineData("one/two/3/4/5/6", "one/two/3/4.Mobile.4", "5/6"),
|
|
InlineData("one/two/3/4/5/6/foo", "one/two/3/4.Mobile.4", "5/6/foo"),
|
|
InlineData("one/two/3/4/5/6/foo.Mobile.htm", null, null)]
|
|
public void MultipleExtensionsMobileTest(string url, string match, string pathInfo)
|
|
{
|
|
string[] files = new[]
|
|
{
|
|
"~/1.one", "~/2.two", "~/1.1/2/3.3", "~/one/two/3/4.4", "~/one/two/3/4/5/6/foo.htm",
|
|
"~/1.Mobile.one", "~/2.Mobile.two", "~/1.1/2/3.Mobile.3", "~/one/two/3/4.Mobile.4", "~/one/two/3/4/5/6/foo.Mobile.htm"
|
|
};
|
|
string[] extensions = new[] { "aspx", "hao", "one", "two", "3", "4" };
|
|
|
|
ConstraintTest(files, extensions, url, match, pathInfo, mobileDevice: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void FilesWithLeadingUnderscoresAreNeverServed()
|
|
{
|
|
string[] files = new[] { "~/hi.evil", "~/_hi.evil", "~/_nest/good.evil", "~/_nest/_hide.evil", "~/_ok.good" };
|
|
string[] extensions = new[] { "evil" };
|
|
|
|
ConstraintTest(files, extensions, "hi", "hi.evil", "");
|
|
ConstraintTest(files, extensions, "_nest/good/some/extra/path/info", "_nest/good.evil", "some/extra/path/info");
|
|
Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_hi", null, null); }, "Files with leading underscores (\"_\") cannot be served.");
|
|
Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_nest/_hide", null, null); }, "Files with leading underscores (\"_\") cannot be served.");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(new object[] { "_foo", "_foo/default.cshtml" })]
|
|
[InlineData(new object[] { "_bar/_baz", "_bar/_baz/index.cshtml" })]
|
|
public void DirectoriesWithLeadingUnderscoresAreServed(string requestPath, string expectedPath)
|
|
{
|
|
// Arramge
|
|
var files = new[] { "~/_foo/default.cshtml", "~/_bar/_baz/index.cshtml" };
|
|
var extensions = new[] { "cshtml" };
|
|
|
|
// Act
|
|
ConstraintTest(files, extensions, requestPath, expectedPath, "");
|
|
}
|
|
|
|
[Fact]
|
|
public void TransformedUnderscoreAreNotServed()
|
|
{
|
|
string[] files = new[] { "~/_ok.Mobile.ext", "~/ok.ext" };
|
|
string[] extensions = new[] { "ext" };
|
|
|
|
ConstraintTest(files, extensions, "ok.ext", "ok.ext", "", mobileDevice: true);
|
|
ConstraintTest(files, extensions, "ok/some/extra/path/info", "ok.ext", "some/extra/path/info", mobileDevice: true);
|
|
|
|
Assert.Throws<HttpException>(() => { ConstraintTest(files, extensions, "_ok.Mobile.ext", null, null, mobileDevice: true); }, "Files with leading underscores (\"_\") cannot be served.");
|
|
}
|
|
|
|
[Fact]
|
|
public void MobileFilesAreReturnedInthePresenceOfUnderscoreFiles()
|
|
{
|
|
string[] files = new[] { "~/_ok.Mobile.ext", "~/ok.ext", "~/ok.mobile.ext" };
|
|
string[] extensions = new[] { "ext" };
|
|
|
|
ConstraintTest(files, extensions, "ok.ext", "ok.Mobile.ext", "", mobileDevice: true);
|
|
ConstraintTest(files, extensions, "ok/some/extra/path/info", "ok.Mobile.ext", "some/extra/path/info", mobileDevice: true);
|
|
ConstraintTest(files, extensions, "ok.mobile", "ok.mobile.ext", "", mobileDevice: false);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnsupportedExtensionExistingFileTest()
|
|
{
|
|
ConstraintTest(new[] { "~/hao.aspx", "~/hao/hao.txt" }, new[] { "aspx" }, "hao/hao.txt", null, null);
|
|
}
|
|
|
|
[Fact]
|
|
public void NullPathValueDoesNotMatchTest()
|
|
{
|
|
ConstraintTest(new[] { "~/hao.aspx", "~/hao/hao.txt" }, new[] { "aspx" }, null, null, null);
|
|
}
|
|
|
|
[Fact]
|
|
public void RightToLeftPrecedenceTest()
|
|
{
|
|
ConstraintTest(new[] { "~/one/two/three.aspx", "~/one/two.aspx", "~/one.aspx" }, new[] { "aspx" }, "one/two/three", "one/two/three.aspx", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultPrecedenceTests()
|
|
{
|
|
string[] files = new[] { "~/one/two/default.aspx", "~/one/default.aspx", "~/default.aspx" };
|
|
string[] extensions = new[] { "aspx" };
|
|
|
|
// Default only tries to look at the full path level
|
|
ConstraintTest(files, extensions, "one/two/three", null, null);
|
|
ConstraintTest(files, extensions, "one/two", "one/two/default.aspx", "");
|
|
ConstraintTest(files, extensions, "one", "one/default.aspx", "");
|
|
ConstraintTest(files, extensions, "", "default.aspx", "");
|
|
ConstraintTest(files, extensions, "one/two/three/four/five/six/7/8", null, null);
|
|
}
|
|
|
|
[Fact]
|
|
public void IndexTests()
|
|
{
|
|
string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/index.aspq" };
|
|
string[] extensions = new[] { "aspx", "aspq" };
|
|
|
|
// index only tries to look at the full path level
|
|
ConstraintTest(files, extensions, "one/two/three", null, null);
|
|
ConstraintTest(files, extensions, "one/two", "one/two/index.aspx", "");
|
|
ConstraintTest(files, extensions, "one", "one/index.aspx", "");
|
|
ConstraintTest(files, extensions, "", "index.aspq", "");
|
|
ConstraintTest(files, extensions, "one/two/three/four/five/six/7/8", null, null);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultVsIndexNestedTest()
|
|
{
|
|
string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/one/default.aspx", "~/index.aspq", "~/default.aspx" };
|
|
string[] extensions = new[] { "aspx", "aspq" };
|
|
|
|
ConstraintTest(files, extensions, "one/two", "one/two/index.aspx", "");
|
|
ConstraintTest(files, extensions, "one", "one/default.aspx", "");
|
|
ConstraintTest(files, extensions, "", "default.aspx", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultVsIndexSameExtensionTest()
|
|
{
|
|
string[] files = new[] { "~/one/two/index.aspx", "~/one/index.aspx", "~/one/default.aspx", "~/index.aspq", "~/default.aspx" };
|
|
string[] extensions = new[] { "aspx" };
|
|
|
|
ConstraintTest(files, extensions, "one", "one/default.aspx", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultVsIndexDifferentExtensionTest()
|
|
{
|
|
string[] files = new[] { "~/index.aspq", "~/default.aspx" };
|
|
string[] extensions = new[] { "aspx", "aspq" };
|
|
|
|
ConstraintTest(files, extensions, "", "default.aspx", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultVsIndexOnlyOneExtensionTest()
|
|
{
|
|
string[] files = new[] { "~/index.aspq", "~/default.aspx" };
|
|
string[] extensions = new[] { "aspq" };
|
|
|
|
ConstraintTest(files, extensions, "", "index.aspq", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void FullMatchNoPathInfoTest()
|
|
{
|
|
ConstraintTest(new[] { "~/hao.aspx" }, new[] { "aspx" }, "hao", "hao.aspx", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void MatchFileWithExtensionTest()
|
|
{
|
|
string[] files = new[] { "~/page.aspq" };
|
|
string[] extensions = new[] { "aspq" };
|
|
|
|
ConstraintTest(files, extensions, "page.aspq", "page.aspq", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void NoMatchFileWithWrongExtensionTest()
|
|
{
|
|
string[] files = new[] { "~/page.aspx" };
|
|
string[] extensions = new[] { "aspq" };
|
|
|
|
ConstraintTest(files, extensions, "page.aspx", null, null);
|
|
}
|
|
|
|
[Fact]
|
|
public void WebPageRouteDoesNotPerformMappingIfRootLevelIsExplicitlyDisabled()
|
|
{
|
|
// Arrange
|
|
var webPageRoute = new WebPageRoute { IsExplicitlyDisabled = true };
|
|
var context = new Mock<HttpContextBase>();
|
|
context.Setup(c => c.RemapHandler(It.IsAny<IHttpHandler>())).Throws(new Exception("Smarty route should be disabled."));
|
|
context.SetupGet(c => c.Request).Throws(new Exception("We do not need to use the request to identify if the app is disabled."));
|
|
|
|
// Act
|
|
webPageRoute.DoPostResolveRequestCache(context.Object);
|
|
|
|
// Assert.
|
|
// If we've come this far, neither of the setups threw.
|
|
Assert.True(true);
|
|
}
|
|
|
|
[Fact]
|
|
public void MatchRequestSetsDisplayModeOfFirstMatchPerContext()
|
|
{
|
|
// Arrange
|
|
var objectFactory = new HashyBuildManager(new string[] { "~/page.Mobile.aspx", "~/nonMobile.aspx" });
|
|
var mockContext = new Mock<HttpContextBase>();
|
|
mockContext.Setup(context => context.Items).Returns(new Hashtable());
|
|
mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
|
|
mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
|
|
mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
|
|
|
|
var displayModeProvider = new DisplayModeProvider();
|
|
|
|
// Act
|
|
WebPageMatch mobileMatch = WebPageRoute.MatchRequest("page.aspx", new string[] { "aspx" }, objectFactory, mockContext.Object, displayModeProvider);
|
|
|
|
// Assert
|
|
Assert.NotNull(mobileMatch.MatchedPath);
|
|
Assert.Equal(DisplayModeProvider.MobileDisplayModeId, DisplayModeProvider.GetDisplayMode(mockContext.Object).DisplayModeId);
|
|
}
|
|
|
|
[Fact]
|
|
public void MatchRequestDoesNotSetDisplayModeIfNoMatch()
|
|
{
|
|
// Arrange
|
|
var objectFactory = new HashyBuildManager(new string[] { "~/page.Mobile.aspx" });
|
|
var mockContext = new Mock<HttpContextBase>();
|
|
mockContext.Setup(context => context.Items).Returns(new Hashtable());
|
|
mockContext.Setup(c => c.Request.Browser.IsMobileDevice).Returns(true);
|
|
mockContext.Setup(c => c.Request.Cookies).Returns(new HttpCookieCollection());
|
|
mockContext.Setup(c => c.Response.Cookies).Returns(new HttpCookieCollection());
|
|
|
|
var displayModeProvider = new DisplayModeProvider();
|
|
var displayMode = new Mock<IDisplayMode>(MockBehavior.Strict);
|
|
displayMode.Setup(d => d.CanHandleContext(mockContext.Object)).Returns(false);
|
|
displayModeProvider.Modes.Add(displayMode.Object);
|
|
|
|
// Act
|
|
WebPageMatch smartyMatch = WebPageRoute.MatchRequest("notThere.aspx", new string[] { "aspx" }, objectFactory, mockContext.Object, displayModeProvider);
|
|
|
|
// Assert
|
|
Assert.Null(DisplayModeProvider.GetDisplayMode(mockContext.Object));
|
|
}
|
|
}
|
|
}
|