// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; using Microsoft.Net.Http.Headers; using Moq; using Xunit; namespace Microsoft.AspNet.Mvc { public class VirtualFileResultTest { [Fact] public void Constructor_SetsFileName() { // Arrange var path = Path.GetFullPath("helllo.txt"); // Act var result = new VirtualFileResult(path, "text/plain"); // Assert Assert.Equal(path, result.FileName); } [Fact] public async Task ExecuteResultAsync_FallsBackToWebRootFileProvider_IfNoFileProviderIsPresent() { // Arrange var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt"); var result = new TestVirtualFileResult(path, "text/plain"); var appEnvironment = new Mock(); appEnvironment.Setup(app => app.WebRootFileProvider) .Returns(GetFileProvider(path)); var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); httpContext.RequestServices = new ServiceCollection() .AddInstance(appEnvironment.Object) .BuildServiceProvider(); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); httpContext.Response.Body.Position = 0; // Assert Assert.NotNull(httpContext.Response.Body); var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); Assert.Equal("FilePathResultTestFile contents¡", contents); } [Fact] public async Task ExecuteResultAsync_FallsbackToStreamCopy_IfNoIHttpSendFilePresent() { // Arrange var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt"); var result = new TestVirtualFileResult(path, "text/plain") { FileProvider = GetFileProvider(path), }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); httpContext.Response.Body.Position = 0; // Assert Assert.NotNull(httpContext.Response.Body); var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); Assert.Equal("FilePathResultTestFile contents¡", contents); } [Fact] public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent() { // Arrange var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt"); var result = new TestVirtualFileResult(path, "text/plain") { FileProvider = GetFileProvider(path), }; var sendFileMock = new Mock(); sendFileMock .Setup(s => s.SendFileAsync(path, 0, null, CancellationToken.None)) .Returns(Task.FromResult(0)); var httpContext = new DefaultHttpContext(); httpContext.Features.Set(sendFileMock.Object); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); // Assert sendFileMock.Verify(); } [Fact] public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding() { // Arrange var expectedContentType = "text/foo; charset=us-ascii"; var result = new TestVirtualFileResult( "FilePathResultTestFile_ASCII.txt", MediaTypeHeaderValue.Parse(expectedContentType)) { FileProvider = GetFileProvider("FilePathResultTestFile_ASCII.txt"), IsAscii = true, }; var httpContext = new DefaultHttpContext(); var memoryStream = new MemoryStream(); httpContext.Response.Body = memoryStream; var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); // Assert var contents = Encoding.ASCII.GetString(memoryStream.ToArray()); Assert.Equal("FilePathResultTestFile contents ASCII encoded", contents); Assert.Equal(expectedContentType, httpContext.Response.ContentType); } [Fact] public async Task ExecuteResultAsync_ReturnsFileContentsForRelativePaths() { // Arrange var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt"); var result = new TestVirtualFileResult(path, "text/plain") { FileProvider = GetFileProvider(path), }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); httpContext.Response.Body.Position = 0; // Assert Assert.NotNull(httpContext.Response.Body); var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); Assert.Equal("FilePathResultTestFile contents¡", contents); } [Theory] [InlineData("FilePathResultTestFile.txt")] [InlineData("TestFiles/FilePathResultTestFile.txt")] [InlineData("TestFiles\\FilePathResultTestFile.txt")] [InlineData("~/FilePathResultTestFile.txt")] [InlineData("~/TestFiles/FilePathResultTestFile.txt")] [InlineData("~/TestFiles\\FilePathResultTestFile.txt")] public async Task ExecuteResultAsync_ReturnsFiles_ForDifferentPaths(string path) { // Arrange var result = new TestVirtualFileResult(path, "text/plain") { FileProvider = GetFileProvider(path), }; var httpContext = new DefaultHttpContext(); var memoryStream = new MemoryStream(); httpContext.Response.Body = memoryStream; var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Act await result.ExecuteResultAsync(context); httpContext.Response.Body.Position = 0; // Assert var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); Assert.Equal("FilePathResultTestFile contents¡", contents); } [Fact] public async Task ExecuteResultAsync_WorksWithNonDiskBasedFiles() { // Arrange var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var expectedData = "This is an embedded resource"; var sourceStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedData)); var nonDiskFileInfo = new Mock(); nonDiskFileInfo.SetupGet(fi => fi.Exists).Returns(true); nonDiskFileInfo.SetupGet(fi => fi.PhysicalPath).Returns(() => null); // set null to indicate non-disk file nonDiskFileInfo.Setup(fi => fi.CreateReadStream()).Returns(sourceStream); var nonDiskFileProvider = new Mock(); nonDiskFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny())).Returns(nonDiskFileInfo.Object); var filePathResult = new VirtualFileResult("/SampleEmbeddedFile.txt", "text/plain") { FileProvider = nonDiskFileProvider.Object }; // Act await filePathResult.ExecuteResultAsync(actionContext); // Assert httpContext.Response.Body.Position = 0; var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync(); Assert.Equal(expectedData, contents); } [Theory] // Root of the file system, forward slash and back slash [InlineData("FilePathResultTestFile.txt")] [InlineData("/FilePathResultTestFile.txt")] [InlineData("\\FilePathResultTestFile.txt")] // '.' has no special meaning [InlineData("./FilePathResultTestFile.txt")] [InlineData(".\\FilePathResultTestFile.txt")] // Traverse to the parent directory and back to the file system directory [InlineData("..\\TestFiles/FilePathResultTestFile.txt")] [InlineData("..\\TestFiles\\FilePathResultTestFile.txt")] [InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")] [InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")] [InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")] [InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")] [InlineData("~/FilePathResultTestFile.txt")] [InlineData("~\\TestFiles\\FilePathResultTestFile.txt")] public async Task ExecuteResultAsync_ThrowsFileNotFound_IfFileProviderCanNotFindTheFile(string path) { // Arrange // Point the IFileProvider root to a different subfolder var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Properties")); var filePathResult = new VirtualFileResult(path, "text/plain") { FileProvider = fileProvider, }; var expectedMessage = "Could not find file: " + path; var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); // Act var ex = await Assert.ThrowsAsync(() => filePathResult.ExecuteResultAsync(context)); // Assert Assert.Equal(expectedMessage, ex.Message); Assert.Equal(path, ex.FileName); } [Theory] [InlineData("/SubFolder/SubFolderTestFile.txt")] [InlineData("\\SubFolder\\SubFolderTestFile.txt")] [InlineData("/SubFolder\\SubFolderTestFile.txt")] [InlineData("\\SubFolder/SubFolderTestFile.txt")] [InlineData("./SubFolder/SubFolderTestFile.txt")] [InlineData(".\\SubFolder\\SubFolderTestFile.txt")] [InlineData("./SubFolder\\SubFolderTestFile.txt")] [InlineData(".\\SubFolder/SubFolderTestFile.txt")] [InlineData("~/SubFolder/SubFolderTestFile.txt")] [InlineData("~/SubFolder\\SubFolderTestFile.txt")] public void ExecuteResultAsync_ThrowsDirectoryNotFound_IfFileProviderCanNotFindTheDirectory(string path) { // Arrange // Point the IFileProvider root to a different subfolder var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Properties")); var filePathResult = new VirtualFileResult(path, "text/plain") { FileProvider = fileProvider, }; var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); // Act & Assert Assert.ThrowsAsync(() => filePathResult.ExecuteResultAsync(context)); } private IFileProvider GetFileProvider(string path) { var fileInfo = new Mock(); fileInfo.SetupGet(fi => fi.Exists).Returns(true); fileInfo.SetupGet(fi => fi.PhysicalPath).Returns(() => path); var fileProvider = new Mock(); fileProvider.Setup(fp => fp.GetFileInfo(It.IsAny())).Returns(fileInfo.Object); return fileProvider.Object; } private class TestVirtualFileResult : VirtualFileResult { public TestVirtualFileResult(string filePath, string contentType) : base(filePath, contentType) { } public TestVirtualFileResult(string filePath, MediaTypeHeaderValue contentType) : base(filePath, contentType) { } public bool IsAscii { get; set; } = false; protected override Stream GetFileStream(IFileInfo fileInfo) { if (IsAscii) { return new MemoryStream(Encoding.ASCII.GetBytes("FilePathResultTestFile contents ASCII encoded")); } else { return new MemoryStream(Encoding.UTF8.GetBytes("FilePathResultTestFile contents¡")); } } } } }