using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Jellyfin.Api.ModelBinders; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Moq; using Xunit; namespace Jellyfin.Api.Tests.ModelBinders { public sealed class CommaDelimitedArrayModelBinderTests { [Fact] public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery() { var queryParamName = "test"; IReadOnlyList queryParamValues = new[] { "lol", "xd" }; var queryParamString = "lol,xd"; var queryParamType = typeof(string[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object?.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery() { var queryParamName = "test"; IReadOnlyList queryParamValues = new[] { 42, 0 }; var queryParamString = "42,0"; var queryParamType = typeof(int[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery() { var queryParamName = "test"; IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString = "How,Much"; var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas() { var queryParamName = "test"; IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString = "How,,Much"; var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery() { var queryParamName = "test"; IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; var queryParamString1 = "How"; var queryParamString2 = "Much"; var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) }, }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery() { var queryParamName = "test"; IReadOnlyList queryParamValues = Array.Empty(); var queryParamType = typeof(TestType[]); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(value: null) }, }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); Assert.Equal((IReadOnlyList?)bindingContextMock.Object.Result.Model, queryParamValues); } [Fact] public async Task BindModelAsync_EnumArrayQuery_BindValidOnly() { var queryParamName = "test"; var queryParamString = "🔥,😢"; var queryParamType = typeof(IReadOnlyList); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(queryParamString) } }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); var listResult = (IReadOnlyList?)bindingContextMock.Object.Result.Model; Assert.NotNull(listResult); Assert.Empty(listResult); } [Fact] public async Task BindModelAsync_EnumArrayQuery_BindValidOnly_2() { var queryParamName = "test"; var queryParamString1 = "How"; var queryParamString2 = "😱"; var queryParamType = typeof(IReadOnlyList); var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger()); var valueProvider = new QueryStringValueProvider( new BindingSource(string.Empty, string.Empty, false, false), new QueryCollection(new Dictionary { { queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) }, }), CultureInfo.InvariantCulture); var bindingContextMock = new Mock(); bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); bindingContextMock.SetupProperty(b => b.Result); await modelBinder.BindModelAsync(bindingContextMock.Object); Assert.True(bindingContextMock.Object.Result.IsModelSet); var listResult = (IReadOnlyList?)bindingContextMock.Object.Result.Model; Assert.NotNull(listResult); Assert.Single(listResult); } } }