// 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; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures { public class PartialViewResultExecutorTest { [Fact] public void FindView_UsesViewEngine_FromPartialViewResult() { // Arrange var context = GetActionContext(); var executor = GetViewExecutor(); var viewName = "my-view"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, viewName, /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())) .Verifiable(); viewEngine .Setup(e => e.FindView(context, viewName, /*isMainPage*/ false)) .Returns(ViewEngineResult.Found(viewName, Mock.Of())) .Verifiable(); var viewResult = new PartialViewResult { ViewEngine = viewEngine.Object, ViewName = viewName, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var viewEngineResult = executor.FindView(context, viewResult); // Assert Assert.Equal(viewName, viewEngineResult.ViewName); viewEngine.Verify(); } [Fact] public void FindView_UsesActionDescriptorName_IfViewNameIsNull() { // Arrange var viewName = "some-view-name"; var context = GetActionContext(viewName); var executor = GetViewExecutor(); var viewResult = new PartialViewResult { ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var viewEngineResult = executor.FindView(context, viewResult); // Assert Assert.Equal(viewName, viewEngineResult.ViewName); } [Fact] [ReplaceCulture("de-CH", "de-CH")] public void FindView_UsesActionDescriptorName_IfViewNameIsNull_UsesInvariantCulture() { // Arrange var viewName = "10/31/2018 07:37:38 -07:00"; var context = GetActionContext(viewName); context.RouteData.Values["action"] = new DateTimeOffset(2018, 10, 31, 7, 37, 38, TimeSpan.FromHours(-7)); var executor = GetViewExecutor(); var viewResult = new PartialViewResult { ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var viewEngineResult = executor.FindView(context, viewResult); // Assert Assert.Equal(viewName, viewEngineResult.ViewName); } [Fact] public void FindView_ReturnsExpectedNotFoundResult_WithGetViewLocations() { // Arrange var expectedLocations = new[] { "location1", "location2" }; var context = GetActionContext(); var executor = GetViewExecutor(); var viewName = "myview"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", expectedLocations)) .Verifiable(); viewEngine .Setup(e => e.FindView(context, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())); var viewResult = new PartialViewResult { ViewName = viewName, ViewEngine = viewEngine.Object, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var result = executor.FindView(context, viewResult); // Assert Assert.False(result.Success); Assert.Null(result.View); Assert.Equal(expectedLocations, result.SearchedLocations); } [Fact] public void FindView_ReturnsExpectedNotFoundResult_WithFindViewLocations() { // Arrange var expectedLocations = new[] { "location1", "location2" }; var context = GetActionContext(); var executor = GetViewExecutor(); var viewName = "myview"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(e => e.FindView(context, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", expectedLocations)); var viewResult = new PartialViewResult { ViewName = viewName, ViewEngine = viewEngine.Object, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var result = executor.FindView(context, viewResult); // Assert Assert.False(result.Success); Assert.Null(result.View); Assert.Equal(expectedLocations, result.SearchedLocations); } [Fact] public void FindView_ReturnsExpectedNotFoundResult_WithAllLocations() { // Arrange var expectedLocations = new[] { "location1", "location2", "location3", "location4" }; var context = GetActionContext(); var executor = GetViewExecutor(); var viewName = "myview"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", new[] { "location1", "location2" })) .Verifiable(); viewEngine .Setup(e => e.FindView(context, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", new[] { "location3", "location4" })); var viewResult = new PartialViewResult { ViewName = viewName, ViewEngine = viewEngine.Object, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var result = executor.FindView(context, viewResult); // Assert Assert.False(result.Success); Assert.Null(result.View); Assert.Equal(expectedLocations, result.SearchedLocations); } [Fact] public void FindView_WritesDiagnostic_ViewFound() { // Arrange var diagnosticSource = new DiagnosticListener("Test"); var listener = new TestDiagnosticListener(); diagnosticSource.SubscribeWithAdapter(listener); var context = GetActionContext(); var executor = GetViewExecutor(diagnosticSource); var viewName = "myview"; var viewResult = new PartialViewResult { ViewName = viewName, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var viewEngineResult = executor.FindView(context, viewResult); // Assert Assert.Equal(viewName, viewEngineResult.ViewName); Assert.NotNull(listener.ViewFound); Assert.NotNull(listener.ViewFound.ActionContext); Assert.NotNull(listener.ViewFound.Result); Assert.NotNull(listener.ViewFound.View); Assert.False(listener.ViewFound.IsMainPage); Assert.Equal("myview", listener.ViewFound.ViewName); } [Fact] public void FindView_WritesDiagnostic_ViewNotFound() { // Arrange var diagnosticSource = new DiagnosticListener("Test"); var listener = new TestDiagnosticListener(); diagnosticSource.SubscribeWithAdapter(listener); var context = GetActionContext(); var executor = GetViewExecutor(diagnosticSource); var viewName = "myview"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) .Verifiable(); viewEngine .Setup(e => e.FindView(context, "myview", /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" })); var viewResult = new PartialViewResult { ViewName = viewName, ViewEngine = viewEngine.Object, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act var viewEngineResult = executor.FindView(context, viewResult); // Assert Assert.False(viewEngineResult.Success); Assert.NotNull(listener.ViewNotFound); Assert.NotNull(listener.ViewNotFound.ActionContext); Assert.NotNull(listener.ViewNotFound.Result); Assert.Equal(new string[] { "location/myview" }, listener.ViewNotFound.SearchedLocations); Assert.Equal("myview", listener.ViewNotFound.ViewName); } [Fact] public async Task ExecuteAsync_UsesContentType_FromPartialViewResult() { // Arrange var context = GetActionContext(); var executor = GetViewExecutor(); var contentType = "application/x-my-content-type"; var viewResult = new PartialViewResult { ViewName = "my-view", ContentType = contentType, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act await executor.ExecuteAsync(context, Mock.Of(), viewResult); // Assert Assert.Equal("application/x-my-content-type", context.HttpContext.Response.ContentType); // Check if the original instance provided by the user has not changed. // Since we do not have access to the new instance created within the view executor, // check if at least the content is the same. Assert.Null(MediaType.GetEncoding(contentType)); } [Fact] public async Task ExecuteAsync_UsesStatusCode_FromPartialViewResult() { // Arrange var context = GetActionContext(); var executor = GetViewExecutor(); var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type"); var viewResult = new PartialViewResult { ViewName = "my-view", StatusCode = 404, ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), TempData = Mock.Of(), }; // Act await executor.ExecuteAsync(context, Mock.Of(), viewResult); // Assert Assert.Equal(404, context.HttpContext.Response.StatusCode); } private ActionContext GetActionContext(string actionName = null) { var routeData = new RouteData(); routeData.Values["action"] = actionName; return new ActionContext(new DefaultHttpContext(), routeData, new ControllerActionDescriptor() { ActionName = actionName }); } private PartialViewResultExecutor GetViewExecutor(DiagnosticSource diagnosticSource = null) { if (diagnosticSource == null) { diagnosticSource = new DiagnosticListener("Test"); } var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(e => e.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns( (executing, name, isMainPage) => ViewEngineResult.NotFound(name, Enumerable.Empty())); viewEngine .Setup(e => e.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns( (context, name, isMainPage) => ViewEngineResult.Found(name, Mock.Of())); var options = Options.Create(new MvcViewOptions()); options.Value.ViewEngines.Add(viewEngine.Object); var viewExecutor = new PartialViewResultExecutor( options, new TestHttpResponseStreamWriterFactory(), new CompositeViewEngine(options), new TempDataDictionaryFactory(new SessionStateTempDataProvider()), diagnosticSource, NullLoggerFactory.Instance, new EmptyModelMetadataProvider()); return viewExecutor; } } }