// 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.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc { public class ConsumesAttributeTests { [Theory] [InlineData("application")] [InlineData("")] public void Constructor_ForInvalidContentType_Throws(string contentType) { // Arrange var expectedMessage = string.Format("The header contains invalid values at index 0: '{0}'", contentType); // Act & Assert var exception = Assert.Throws(() => new ConsumesAttribute(contentType)); Assert.Equal(expectedMessage, exception.Message); } [Theory] [InlineData("", "")] [InlineData("application/xml,, application/json", "")] [InlineData(", application/json", "")] [InlineData("invalid", "invalid")] [InlineData("application/xml,invalid, application/json", "invalid")] [InlineData("invalid, application/json", "invalid")] public void Constructor_UnparsableContentType_Throws(string content, string invalidContentType) { // Act var contentTypes = content.Split(',').Select(contentType => contentType.Trim()).ToArray(); // Assert var ex = Assert.Throws( () => new ConsumesAttribute(contentTypes[0], contentTypes.Skip(1).ToArray())); Assert.Equal("The header contains invalid values at index 0: '" + (invalidContentType ?? "") + "'", ex.Message); } [Theory] [InlineData("application/*", "application/*")] [InlineData("application/xml, application/*, application/json", "application/*")] [InlineData("application/*, application/json", "application/*")] [InlineData("*/*", "*/*")] [InlineData("application/xml, */*, application/json", "*/*")] [InlineData("*/*, application/json", "*/*")] public void Constructor_InvalidContentType_Throws(string content, string invalidContentType) { // Act var contentTypes = content.Split(',').Select(contentType => contentType.Trim()).ToArray(); // Assert var ex = Assert.Throws( () => new ConsumesAttribute(contentTypes[0], contentTypes.Skip(1).ToArray())); Assert.Equal( string.Format("The argument '{0}' is invalid. " + "Media types which match all types or match all subtypes are not supported.", invalidContentType), ex.Message); } [Theory] [InlineData("application/json")] [InlineData("application/json;Parameter1=12")] [InlineData("text/xml")] public void ActionConstraint_Accept_MatchesForMatchingRequestContentType(string contentType) { // Arrange var constraint = new ConsumesAttribute("application/json", "text/xml"); var action = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint, FilterScope.Action) } }; var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(action, new [] { constraint }), }; context.CurrentCandidate = context.Candidates[0]; context.RouteContext = CreateRouteContext(contentType: contentType); // Act & Assert Assert.True(constraint.Accept(context)); } [Fact] public void ActionConstraint_Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches() { // Arrange var constraint1 = new ConsumesAttribute("application/json", "text/xml"); var action1 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; var constraint2 = new Mock(); var action2 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint2.Object, FilterScope.Action) } }; constraint2.Setup(o => o.Accept(It.IsAny())) .Returns(true); var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(action1, new [] { constraint1 }), new ActionSelectorCandidate(action2, new [] { constraint2.Object }), }; context.CurrentCandidate = context.Candidates[0]; context.RouteContext = CreateRouteContext(contentType: "application/custom"); // Act & Assert Assert.False(constraint1.Accept(context)); } [Theory] [InlineData("application/custom")] [InlineData("")] [InlineData(null)] public void ActionConstraint_Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json", "text/xml"); var action1 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; var constraint2 = new Mock(); var action2 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint2.Object, FilterScope.Action) } }; constraint2.Setup(o => o.Accept(It.IsAny())) .Returns(false); var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(action1, new [] { constraint1 }), new ActionSelectorCandidate(action2, new [] { constraint2.Object }), }; context.CurrentCandidate = context.Candidates[0]; context.RouteContext = CreateRouteContext(contentType: contentType); // Act & Assert Assert.True(constraint1.Accept(context)); } [Theory] [InlineData("")] [InlineData(null)] public void ActionConstraint_Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json"); var actionWithConstraint = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; var constraint2 = new ConsumesAttribute("text/xml"); var actionWithConstraint2 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint2, FilterScope.Action) } }; var actionWithoutConstraint = new ActionDescriptor(); var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }), new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }), new ActionSelectorCandidate(actionWithoutConstraint, new List()), }; context.RouteContext = CreateRouteContext(contentType: contentType); // Act & Assert context.CurrentCandidate = context.Candidates[0]; Assert.False(constraint1.Accept(context)); context.CurrentCandidate = context.Candidates[1]; Assert.False(constraint2.Accept(context)); } [Theory] [InlineData("application/xml")] [InlineData("application/custom")] [InlineData("invalid/invalid")] public void ActionConstraint_Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) { // Arrange var actionWithoutConstraint = new ActionDescriptor(); var constraint1 = new ConsumesAttribute("application/json"); var actionWithConstraint = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; var constraint2 = new ConsumesAttribute("text/xml"); var actionWithConstraint2 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint2, FilterScope.Action) } }; var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }), new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }), new ActionSelectorCandidate(actionWithoutConstraint, new List()), }; context.RouteContext = CreateRouteContext(contentType: contentType); // Act & Assert context.CurrentCandidate = context.Candidates[0]; Assert.False(constraint1.Accept(context)); context.CurrentCandidate = context.Candidates[1]; Assert.False(constraint2.Accept(context)); } [Theory] [InlineData("")] [InlineData(null)] public void ActionConstraint_Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json"); var actionWithConstraint = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; var constraint2 = new ConsumesAttribute("text/xml"); var actionWithConstraint2 = new ActionDescriptor() { FilterDescriptors = new List() { new FilterDescriptor(constraint2, FilterScope.Action) } }; var actionWithoutConstraint = new ActionDescriptor(); var context = new ActionConstraintContext(); context.Candidates = new List() { new ActionSelectorCandidate(actionWithConstraint, new [] { constraint1 }), new ActionSelectorCandidate(actionWithConstraint2, new [] { constraint2 }), }; context.RouteContext = CreateRouteContext(contentType: contentType); // Act & Assert context.CurrentCandidate = context.Candidates[0]; Assert.True(constraint1.Accept(context)); context.CurrentCandidate = context.Candidates[1]; Assert.True(constraint2.Accept(context)); } [Theory] [InlineData("application/xml")] [InlineData("application/custom")] public void OnResourceExecuting_ForNoContentTypeMatch_SetsUnsupportedMediaTypeResult(string contentType) { // Arrange var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = contentType; var consumesFilter = new ConsumesAttribute("application/json"); var actionWithConstraint = new ActionDescriptor() { ActionConstraints = new List() { consumesFilter }, FilterDescriptors = new List() { new FilterDescriptor(consumesFilter, FilterScope.Action) } }; var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint); var resourceExecutingContext = new ResourceExecutingContext( actionContext, new[] { consumesFilter }, new List()); // Act consumesFilter.OnResourceExecuting(resourceExecutingContext); // Assert Assert.NotNull(resourceExecutingContext.Result); Assert.IsType(resourceExecutingContext.Result); } [Theory] [InlineData("")] [InlineData(null)] public void OnResourceExecuting_NullOrEmptyRequestContentType_SetsUnsupportedMediaTypeResult(string contentType) { // Arrange var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = contentType; var consumesFilter = new ConsumesAttribute("application/json"); var actionWithConstraint = new ActionDescriptor() { ActionConstraints = new List() { consumesFilter }, FilterDescriptors = new List() { new FilterDescriptor(consumesFilter, FilterScope.Action) } }; var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint); var resourceExecutingContext = new ResourceExecutingContext( actionContext, new[] { consumesFilter }, new List()); // Act consumesFilter.OnResourceExecuting(resourceExecutingContext); // Assert Assert.NotNull(resourceExecutingContext.Result); Assert.IsType(resourceExecutingContext.Result); } [Theory] [InlineData("application/xml")] [InlineData("application/json")] public void OnResourceExecuting_ForAContentTypeMatch_IsNoOp(string contentType) { // Arrange var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = contentType; var consumesFilter = new ConsumesAttribute("application/json", "application/xml"); var actionWithConstraint = new ActionDescriptor() { ActionConstraints = new List() { consumesFilter }, FilterDescriptors = new List() { new FilterDescriptor(consumesFilter, FilterScope.Action) } }; var actionContext = new ActionContext(httpContext, new RouteData(), actionWithConstraint); var resourceExecutingContext = new ResourceExecutingContext( actionContext, new[] { consumesFilter }, new List()); // Act consumesFilter.OnResourceExecuting(resourceExecutingContext); // Assert Assert.Null(resourceExecutingContext.Result); } [Fact] public void SetContentTypes_ClearsAndSetsContentTypes() { // Arrange var attribute = new ConsumesAttribute("application/json", "text/json"); var contentTypes = new MediaTypeCollection() { MediaTypeHeaderValue.Parse("application/xml"), MediaTypeHeaderValue.Parse("text/xml"), }; // Act attribute.SetContentTypes(contentTypes); // Assert Assert.Collection( contentTypes.OrderBy(t => t), t => Assert.Equal("application/json", t), t => Assert.Equal("text/json", t)); } private static RouteContext CreateRouteContext(string contentType = null, object routeValues = null) { var httpContext = CreateHttpContext(contentType); var routeContext = new RouteContext(httpContext); routeContext.RouteData = new RouteData(); foreach (var kvp in new RouteValueDictionary(routeValues)) { routeContext.RouteData.Values.Add(kvp.Key, kvp.Value); } return routeContext; } private static HttpContext CreateHttpContext(string contentType = null, object routeValues = null) { var httpContext = new DefaultHttpContext(); if (contentType != null) { httpContext.Request.ContentType = contentType; } return httpContext; } public interface ITestActionConsumeConstraint : IConsumesActionConstraint, IResourceFilter { } } }