// 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.Collections.Generic; using System.Linq; using System.Net.Http.Formatting.Mocks; using System.Net.Http.Headers; using System.Text; using Microsoft.AspNetCore.Testing; using Microsoft.TestCommon; using Newtonsoft.Json.Linq; using Xunit; namespace System.Net.Http.Formatting { public class DefaultContentNegotiatorTests { private readonly DefaultContentNegotiator _negotiator = new DefaultContentNegotiator(); private readonly HttpRequestMessage _request = new HttpRequestMessage(); public static TheoryData MatchRequestMediaTypeData { get { // string requestMediaType, string[] supportedMediaTypes, string expectedMediaType return new TheoryData { { "text/plain", new string[0], null }, { "text/plain", new string[] { "text/xml", "application/xml" }, null }, { "application/xml", new string[] { "application/xml", "text/xml" }, "application/xml" }, { "APPLICATION/XML", new string[] { "text/xml", "application/xml" }, "application/xml" }, { "application/xml; charset=utf-8", new string[] { "text/xml", "application/xml" }, "application/xml" }, { "application/xml; charset=utf-8; parameter=value", new string[] { "text/xml", "application/xml" }, "application/xml" }, }; } } public static TheoryData MatchAcceptHeaderData { get { // string[] acceptHeader, string[] supportedMediaTypes, string expectedMediaType, double matchQuality, int range return new TheoryData { { new string[] { "text/plain" }, new string[0], null, 0.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "text/plain" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "text/plain; q=0.5" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "application/xml" }, new string[] { "application/xml", "text/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "APPLICATION/XML; q=0.5" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "text/xml; q=0.5", "APPLICATION/XML; q=0.7" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.7, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "application/xml; q=0.0" }, new string[] { "application/xml", "text/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "APPLICATION/XML; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "text/xml; q=0.0", "APPLICATION/XML; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "text/*" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange }, { new string[] { "text/*", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "text/*", "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange }, { new string[] { "text/*; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange }, { new string[] { "text/*; q=0.5", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "text/*; q=0.0", "application/xml; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "text/*; q=0.0", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "*/*; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange }, { new string[] { "*/*; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None }, { new string[] { "*/*; q=0.5", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "*/*; q=1.0", "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange }, { new string[] { "*/*", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "text/*; q=0.5", "*/*; q=0.2", "application/xml; q=1.0" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, { new string[] { "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral }, }; } } public static TheoryData ShouldMatchOnTypeData { get { // bool excludeMatchOnType, string[] acceptHeaders, bool expectedResult return new TheoryData { { false, new string[0], true }, { true, new string[0], true }, { false, new string[] { "application/xml" }, true }, { true, new string[] { "application/xml" }, false }, { false, new string[] { "application/xml; q=1.0" }, true }, { true, new string[] { "application/xml; q=1.0" }, false }, { false, new string[] { "application/xml; q=0.0" }, true }, { true, new string[] { "application/xml; q=0.0" }, false }, { false, new string[] { "application/xml; q=0.0", "application/json" }, true }, { true, new string[] { "application/xml; q=0.0", "application/json" }, false }, { false, new string[] { "text/nomatch" }, true }, { true, new string[] { "text/nomatch" }, false }, }; } } public static TheoryData MatchTypeData { get { // string[] supportedMediaTypes, string expectedMediaType return new TheoryData { { new string[0], "application/octet-stream" }, { new string[] { "text/xml", "application/xml" }, "text/xml" }, { new string[] { "application/xml", "text/xml" }, "application/xml" }, }; } } public static TheoryData SelectResponseCharacterEncodingData { get { // string[] acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding return new TheoryData { { new string[] { "utf-8" }, null, new string[0], null }, { new string[0], "utf-8", new string[0], null }, { new string[0], null, new string[] { "utf-8", "utf-16"}, "utf-8" }, { new string[0], "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" }, { new string[] { "utf-8" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" }, { new string[] { "utf-16" }, "utf-8", new string[] { "utf-8", "utf-16"}, "utf-16" }, { new string[] { "utf-16; q=0.5" }, "utf-8", new string[] { "utf-8", "utf-16"}, "utf-16" }, { new string[] { "utf-8; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" }, { new string[] { "utf-8; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" }, { new string[] { "utf-8; q=0.0", "utf-16; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" }, { new string[] { "utf-8; q=0.0", "utf-16; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" }, { new string[] { "*; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" }, { new string[] { "*; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" }, }; } } public static TheoryData, MediaTypeFormatterMatch> SelectResponseMediaTypeData { get { MediaTypeFormatterMatch matchAccept10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral); MediaTypeFormatterMatch matchAccept05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral); MediaTypeFormatterMatch matchAcceptSubTypeRange10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange); MediaTypeFormatterMatch matchAcceptSubTypeRange05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange); MediaTypeFormatterMatch matchAcceptAllRange10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange); MediaTypeFormatterMatch matchAcceptAllRange05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange); MediaTypeFormatterMatch matchRequest10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestMediaType); MediaTypeFormatterMatch matchType10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnCanWriteType); // ICollection candidateMatches, MediaTypeFormatterMatch winner return new TheoryData, MediaTypeFormatterMatch> { { new List(), null }, { new List() { matchType10 }, matchType10 }, { new List() { matchType10, matchRequest10 }, matchRequest10 }, { new List() { matchType10, matchRequest10, matchAcceptAllRange10 }, matchAcceptAllRange10 }, { new List() { matchType10, matchRequest10, matchAcceptAllRange10, matchAcceptSubTypeRange10 }, matchAcceptSubTypeRange10 }, { new List() { matchType10, matchRequest10, matchAcceptAllRange10, matchAcceptSubTypeRange10, matchAccept10 }, matchAccept10 }, { new List() { matchAccept05, matchAccept10 }, matchAccept10 }, { new List() { matchAccept10, matchAccept05 }, matchAccept10 }, { new List() { matchAcceptSubTypeRange05, matchAcceptSubTypeRange10 }, matchAcceptSubTypeRange10 }, { new List() { matchAcceptSubTypeRange10, matchAcceptSubTypeRange05 }, matchAcceptSubTypeRange10 }, { new List() { matchAcceptAllRange05, matchAcceptAllRange10 }, matchAcceptAllRange10 }, { new List() { matchAcceptAllRange10, matchAcceptAllRange05 }, matchAcceptAllRange10 }, }; } } public static TheoryData UpdateBestMatchData { get { MediaTypeFormatterMatch matchMapping10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.None); MediaTypeFormatterMatch matchMapping05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.None); // MediaTypeFormatterMatch current, MediaTypeFormatterMatch potentialReplacement, currentWins return new TheoryData { { null, matchMapping10, false }, { null, matchMapping05, false }, { matchMapping10, matchMapping10, true }, { matchMapping10, matchMapping05, true }, { matchMapping05, matchMapping10, false }, { matchMapping05, matchMapping05, true }, }; } } private static MediaTypeFormatterMatch CreateMatch(double? quality, MediaTypeFormatterMatchRanking ranking) { MockMediaTypeFormatter formatter = new MockMediaTypeFormatter(); MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/test"); return new MediaTypeFormatterMatch(formatter, mediaType, quality, ranking); } [Fact] public void TypeIsCorrect() { new TypeAssert().HasProperties(typeof(DefaultContentNegotiator), TypeAssert.TypeProperties.IsPublicVisibleClass); } [Fact] public void Negotiate_ForEmptyFormatterCollection_ReturnsNull() { var result = _negotiator.Negotiate(typeof(string), _request, Enumerable.Empty()); Assert.Null(result); } [Fact] public void Negotiate_ForRequestReturnsFirstMatchingFormatter() { MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/myMediaType"); MediaTypeFormatter formatter1 = new MockMediaTypeFormatter() { CanWriteTypeCallback = (Type t) => false }; MediaTypeFormatter formatter2 = new MockMediaTypeFormatter() { CanWriteTypeCallback = (Type t) => true }; formatter2.SupportedMediaTypes.Add(mediaType); MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection( new MediaTypeFormatter[] { formatter1, formatter2 }); _request.Content = new StringContent("test", Encoding.UTF8, mediaType.MediaType); var result = _negotiator.Negotiate(typeof(string), _request, collection); Assert.Same(formatter2, result.Formatter); new MediaTypeAssert().AreEqual(mediaType, result.MediaType, "Expected the formatter's media type to be returned."); } [Fact] public void Negotiate_SelectsJsonAsDefaultFormatter() { // Arrange _request.Content = new StringContent("test"); // Act var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert Assert.IsType(result.Formatter); Assert.Equal(MediaTypeConstants.ApplicationJsonMediaType.MediaType, result.MediaType.MediaType); } [Fact] public void Negotiate_SelectsXmlFormatter_ForXhrRequestThatAcceptsXml() { // Arrange _request.Content = new StringContent("test"); _request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); _request.Headers.Add("x-requested-with", "XMLHttpRequest"); // Act var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert Assert.Equal("application/xml", result.MediaType.MediaType); Assert.IsType(result.Formatter); } [Fact] public void Negotiate_SelectsJsonFormatter_ForXhrRequestThatDoesNotSpecifyAcceptHeaders() { // Arrange _request.Content = new StringContent("test"); _request.Headers.Add("x-requested-with", "XMLHttpRequest"); // Act var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert Assert.Equal("application/json", result.MediaType.MediaType); Assert.IsType(result.Formatter); } [Fact] public void Negotiate_SelectsJsonFormatter_ForXHRAndJsonValueResponse() { // Arrange _request.Content = new StringContent("test"); _request.Headers.Add("x-requested-with", "XMLHttpRequest"); // Act // Mono issue - https://github.com/aspnet/External/issues/27 var type = TestPlatformHelper.IsMono ? typeof(string) : typeof(JToken); var result = _negotiator.Negotiate(type, _request, new MediaTypeFormatterCollection()); Assert.Equal("application/json", result.MediaType.MediaType); Assert.IsType(result.Formatter); } [Fact] public void Negotiate_SelectsJsonFormatter_ForXHRAndMatchAllAcceptHeader() { // Accept _request.Content = new StringContent("test"); _request.Headers.Add("x-requested-with", "XMLHttpRequest"); _request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); // Act var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert Assert.Equal("application/json", result.MediaType.MediaType); Assert.IsType(result.Formatter); } [Fact] public void Negotiate_UsesRequestedFormatterForXHRAndMatchAllPlusOtherAcceptHeader() { // Arrange _request.Content = new StringContent("test"); _request.Headers.Add("x-requested-with", "XMLHttpRequest"); _request.Headers.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); // XHR header sent by Firefox 3b5 // Act var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert Assert.Equal("application/xml", result.MediaType.MediaType); Assert.IsType(result.Formatter); } [Theory] [InlineData(true)] [InlineData(false)] public void Negotiate_ObservesExcludeMatchOnTypeOnly(bool excludeMatchOnTypeOnly) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(excludeMatchOnTypeOnly); _request.Content = new StringContent("test"); _request.Headers.Accept.ParseAdd("text/html"); // Act var result = negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection()); // Assert if (excludeMatchOnTypeOnly) { Assert.Null(result); } else { Assert.NotNull(result); Assert.Equal("application/json", result.MediaType.MediaType); } } [Theory] [MemberData(nameof(MatchAcceptHeaderData))] public void MatchAcceptHeader_ReturnsMatch(string[] acceptHeaders, string[] supportedMediaTypes, string expectedMediaType, double expectedQuality, int ranking) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); List unsortedAcceptHeaders = acceptHeaders.Select(a => MediaTypeWithQualityHeaderValue.Parse(a)).ToList(); IEnumerable sortedAcceptHeaders = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedAcceptHeaders); MockMediaTypeFormatter formatter = new MockMediaTypeFormatter(); foreach (string supportedMediaType in supportedMediaTypes) { formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType)); } // Act MediaTypeFormatterMatch match = negotiator.MatchAcceptHeader(sortedAcceptHeaders, formatter); // Assert if (expectedMediaType == null) { Assert.Null(match); } else { Assert.Same(formatter, match.Formatter); Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType); Assert.Equal(expectedQuality, match.Quality); Assert.Equal(ranking, (int)match.Ranking); } } [Theory] [MemberData(nameof(MatchRequestMediaTypeData))] public void MatchRequestMediaType_ReturnsMatch(string requestMediaType, string[] supportedMediaTypes, string expectedMediaType) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); HttpRequestMessage request = new HttpRequestMessage(); request.Content = new StringContent(String.Empty); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(requestMediaType); MockMediaTypeFormatter formatter = new MockMediaTypeFormatter(); foreach (string supportedMediaType in supportedMediaTypes) { formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType)); } // Act MediaTypeFormatterMatch match = negotiator.MatchRequestMediaType(request, formatter); // Assert if (expectedMediaType == null) { Assert.Null(match); } else { Assert.Same(formatter, match.Formatter); Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType); Assert.Equal(1.0, match.Quality); Assert.Equal(MediaTypeFormatterMatchRanking.MatchOnRequestMediaType, match.Ranking); } } [Theory] [MemberData(nameof(ShouldMatchOnTypeData))] public void ShouldMatchOnType_ReturnsExpectedResult(bool excludeMatchOnType, string[] acceptHeaders, bool expectedResult) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(excludeMatchOnType); List unsortedAcceptHeaders = acceptHeaders.Select(a => MediaTypeWithQualityHeaderValue.Parse(a)).ToList(); IEnumerable sortedAcceptHeaders = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedAcceptHeaders); // Act bool result = negotiator.ShouldMatchOnType(sortedAcceptHeaders); // Assert Assert.Equal(expectedResult, result); } [Theory] [MemberData(nameof(MatchTypeData))] public void MatchType_ReturnsMatch(string[] supportedMediaTypes, string expectedMediaType) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); MockMediaTypeFormatter formatter = new MockMediaTypeFormatter(); foreach (string supportedMediaType in supportedMediaTypes) { formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType)); } // Act MediaTypeFormatterMatch match = negotiator.MatchType(typeof(object), formatter); // Assert Assert.Same(formatter, match.Formatter); Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType); Assert.Equal(1.0, match.Quality); Assert.Equal(MediaTypeFormatterMatchRanking.MatchOnCanWriteType, match.Ranking); } [Theory] [MemberData(nameof(SelectResponseMediaTypeData))] public void SelectResponseMediaTypeFormatter_SelectsMediaType(ICollection matches, MediaTypeFormatterMatch expectedWinner) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); // Act MediaTypeFormatterMatch actualWinner = negotiator.SelectResponseMediaTypeFormatter(matches); // Assert Assert.Same(expectedWinner, actualWinner); } [Theory] [MemberData(nameof(SelectResponseCharacterEncodingData))] public void SelectResponseCharacterEncoding_SelectsEncoding(string[] acceptCharsetHeaders, string requestEncoding, string[] supportedEncodings, string expectedEncoding) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); HttpRequestMessage request = new HttpRequestMessage(); foreach (string acceptCharsetHeader in acceptCharsetHeaders) { request.Headers.AcceptCharset.Add(StringWithQualityHeaderValue.Parse(acceptCharsetHeader)); } if (requestEncoding != null) { Encoding reqEncoding = Encoding.GetEncoding(requestEncoding); StringContent content = new StringContent("", reqEncoding, "text/plain"); request.Content = content; } MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true }; foreach (string supportedEncoding in supportedEncodings) { formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding)); } // Act Encoding actualEncoding = negotiator.SelectResponseCharacterEncoding(request, formatter); // Assert if (expectedEncoding == null) { Assert.Null(actualEncoding); } else { Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding); } } [Theory] [TestDataSet(typeof(DefaultContentNegotiatorTests), nameof(MediaTypeWithQualityHeaderValueComparerTestsBeforeAfterSortedValues))] public void SortMediaTypeWithQualityHeaderValuesByQFactor_SortsCorrectly(IEnumerable unsorted, IEnumerable expectedSorted) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); List unsortedValues = new List(unsorted.Select(u => MediaTypeWithQualityHeaderValue.Parse(u))); List expectedSortedValues = new List(expectedSorted.Select(u => MediaTypeWithQualityHeaderValue.Parse(u))); // Act IEnumerable actualSorted = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedValues); // Assert Assert.True(expectedSortedValues.SequenceEqual(actualSorted)); } public static TheoryData MediaTypeWithQualityHeaderValueComparerTestsBeforeAfterSortedValues { get { return new TheoryData { { new string[] { "application/*", "text/plain", "text/plain;q=1.0", "text/plain", "text/plain;q=0", "*/*;q=0.8", "*/*;q=1", "text/*;q=1", "text/plain;q=0.8", "text/*;q=0.8", "text/*;q=0.6", "text/*;q=1.0", "*/*;q=0.4", "text/plain;q=0.6", "text/xml", }, new string[] { "text/plain", "text/plain;q=1.0", "text/plain", "text/xml", "application/*", "text/*;q=1", "text/*;q=1.0", "*/*;q=1", "text/plain;q=0.8", "text/*;q=0.8", "*/*;q=0.8", "text/plain;q=0.6", "text/*;q=0.6", "*/*;q=0.4", "text/plain;q=0", } } }; } } [Theory] [TestDataSet(typeof(DefaultContentNegotiatorTests), nameof(StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues))] public void SortStringWithQualityHeaderValuesByQFactor_SortsCorrectly(IEnumerable unsorted, IEnumerable expectedSorted) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); List unsortedValues = new List(unsorted.Select(u => StringWithQualityHeaderValue.Parse(u))); List expectedSortedValues = new List(expectedSorted.Select(u => StringWithQualityHeaderValue.Parse(u))); // Act IEnumerable actualSorted = negotiator.SortStringWithQualityHeaderValuesByQFactor(unsortedValues); // Assert Assert.True(expectedSortedValues.SequenceEqual(actualSorted)); } public static TheoryData StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues { get { return new TheoryData { { new string[] { "text", "text;q=1.0", "text", "text;q=0", "*;q=0.8", "*;q=1", "text;q=0.8", "*;q=0.6", "text;q=1.0", "*;q=0.4", "text;q=0.6", }, new string[] { "text", "text;q=1.0", "text", "text;q=1.0", "*;q=1", "text;q=0.8", "*;q=0.8", "text;q=0.6", "*;q=0.6", "*;q=0.4", "text;q=0", } } }; } } [Theory] [MemberData(nameof(UpdateBestMatchData))] public void UpdateBestMatch_SelectsCorrectly(MediaTypeFormatterMatch current, MediaTypeFormatterMatch replacement, bool currentWins) { // Arrange MockContentNegotiator negotiator = new MockContentNegotiator(); // Act MediaTypeFormatterMatch actualResult = negotiator.UpdateBestMatch(current, replacement); // Assert if (currentWins) { Assert.Same(current, actualResult); } else { Assert.Same(replacement, actualResult); } } private class PlainTextFormatter : MediaTypeFormatter { public override bool CanReadType(Type type) { return true; } public override bool CanWriteType(Type type) { return true; } } } }