From 2db0750abbcb3994a6d6163652566fe5e0e7c7b7 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 14 Feb 2025 04:24:55 +0100 Subject: Make the JsonConverters for delimited arrays more generic (#13396) * Make the JsonConverters for delimited arrays more generic Also adds some tests for serialization (with different types) as we didn't have any before. * Ignore warnings --- .../CommaDelimitedArrayModelBinderTests.cs | 230 --------------------- .../CommaDelimitedCollectionModelBinderTests.cs | 230 +++++++++++++++++++++ .../PipeDelimitedArrayModelBinderTests.cs | 230 --------------------- .../PipeDelimitedCollectionModelBinderTests.cs | 230 +++++++++++++++++++++ .../Converters/JsonCommaDelimitedArrayTests.cs | 135 ------------ .../JsonCommaDelimitedCollectionTests.cs | 208 +++++++++++++++++++ .../JsonCommaDelimitedIReadOnlyListTests.cs | 13 ++ .../Json/Models/GenericBodyArrayModel.cs | 2 +- .../Models/GenericBodyIReadOnlyCollectionModel.cs | 19 ++ .../Json/Models/GenericBodyIReadOnlyListModel.cs | 2 +- .../Json/Models/GenericBodyListModel.cs | 22 ++ 11 files changed, 724 insertions(+), 597 deletions(-) delete mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedCollectionModelBinderTests.cs delete mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedCollectionModelBinderTests.cs delete mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedCollectionTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyCollectionModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyListModel.cs (limited to 'tests') diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs deleted file mode 100644 index e37c9d91f3..0000000000 --- a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs +++ /dev/null @@ -1,230 +0,0 @@ -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); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedCollectionModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedCollectionModelBinderTests.cs new file mode 100644 index 0000000000..e6b9acfe19 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedCollectionModelBinderTests.cs @@ -0,0 +1,230 @@ +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 CommaDelimitedCollectionModelBinderTests + { + [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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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 CommaDelimitedCollectionModelBinder(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); + } + } +} diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs deleted file mode 100644 index 7c05ee0362..0000000000 --- a/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedArrayModelBinderTests.cs +++ /dev/null @@ -1,230 +0,0 @@ -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 PipeDelimitedArrayModelBinderTests - { - [Fact] - public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedStringArrayQuery() - { - var queryParamName = "test"; - IReadOnlyList queryParamValues = new[] { "lol", "xd" }; - var queryParamString = "lol|xd"; - var queryParamType = typeof(string[]); - - var modelBinder = new PipeDelimitedArrayModelBinder(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_CorrectlyBindsValidDelimitedIntArrayQuery() - { - var queryParamName = "test"; - IReadOnlyList queryParamValues = new[] { 42, 0 }; - var queryParamString = "42|0"; - var queryParamType = typeof(int[]); - - var modelBinder = new PipeDelimitedArrayModelBinder(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_CorrectlyBindsValidPipeDelimitedEnumArrayQuery() - { - var queryParamName = "test"; - IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; - var queryParamString = "How|Much"; - var queryParamType = typeof(TestType[]); - - var modelBinder = new PipeDelimitedArrayModelBinder(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_CorrectlyBindsValidPipeDelimitedEnumArrayQueryWithDoublePipes() - { - var queryParamName = "test"; - IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; - var queryParamString = "How||Much"; - var queryParamType = typeof(TestType[]); - - var modelBinder = new PipeDelimitedArrayModelBinder(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 PipeDelimitedArrayModelBinder(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 PipeDelimitedArrayModelBinder(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 PipeDelimitedArrayModelBinder(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 PipeDelimitedArrayModelBinder(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); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedCollectionModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedCollectionModelBinderTests.cs new file mode 100644 index 0000000000..941f4f12cc --- /dev/null +++ b/tests/Jellyfin.Api.Tests/ModelBinders/PipeDelimitedCollectionModelBinderTests.cs @@ -0,0 +1,230 @@ +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 PipeDelimitedCollectionModelBinderTests + { + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedStringArrayQuery() + { + var queryParamName = "test"; + IReadOnlyList queryParamValues = new[] { "lol", "xd" }; + var queryParamString = "lol|xd"; + var queryParamType = typeof(string[]); + + var modelBinder = new PipeDelimitedCollectionModelBinder(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_CorrectlyBindsValidDelimitedIntArrayQuery() + { + var queryParamName = "test"; + IReadOnlyList queryParamValues = new[] { 42, 0 }; + var queryParamString = "42|0"; + var queryParamType = typeof(int[]); + + var modelBinder = new PipeDelimitedCollectionModelBinder(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_CorrectlyBindsValidPipeDelimitedEnumArrayQuery() + { + var queryParamName = "test"; + IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; + var queryParamString = "How|Much"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new PipeDelimitedCollectionModelBinder(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_CorrectlyBindsValidPipeDelimitedEnumArrayQueryWithDoublePipes() + { + var queryParamName = "test"; + IReadOnlyList queryParamValues = new[] { TestType.How, TestType.Much }; + var queryParamString = "How||Much"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new PipeDelimitedCollectionModelBinder(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 PipeDelimitedCollectionModelBinder(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 PipeDelimitedCollectionModelBinder(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 PipeDelimitedCollectionModelBinder(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 PipeDelimitedCollectionModelBinder(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); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs deleted file mode 100644 index d247b8cb18..0000000000 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using Jellyfin.Extensions.Tests.Json.Models; -using MediaBrowser.Model.Session; -using Xunit; - -namespace Jellyfin.Extensions.Tests.Json.Converters -{ - public class JsonCommaDelimitedArrayTests - { - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() - { - Converters = - { - new JsonStringEnumConverter() - } - }; - - [Fact] - public void Deserialize_String_Null_Success() - { - var value = JsonSerializer.Deserialize>(@"{ ""Value"": null }", _jsonOptions); - Assert.Null(value?.Value); - } - - [Fact] - public void Deserialize_Empty_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = Array.Empty() - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": """" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_String_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = ["a", "b", "c"] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_String_Space_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = ["a", "b", "c"] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_GenericCommandType_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_GenericCommandType_EmptyEntry_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_GenericCommandType_Invalid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAValidCommand,MoveDown"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_GenericCommandType_Space_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_String_Array_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = ["a", "b", "c"] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public void Deserialize_GenericCommandType_Array_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel - { - Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] - }; - - var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); - Assert.Equal(desiredValue.Value, value?.Value); - } - } -} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedCollectionTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedCollectionTests.cs new file mode 100644 index 0000000000..83f917c17f --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedCollectionTests.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Tests.Json.Models; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonCommaDelimitedCollectionTests + { + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() + { + Converters = + { + new JsonStringEnumConverter() + } + }; + + [Fact] + public void Deserialize_String_Null_Success() + { + var value = JsonSerializer.Deserialize>(@"{ ""Value"": null }", _jsonOptions); + Assert.Null(value?.Value); + } + + [Fact] + public void Deserialize_Empty_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = Array.Empty() + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": """" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_EmptyList_Success() + { + var desiredValue = new GenericBodyListModel + { + Value = [] + }; + + Assert.Throws(() => JsonSerializer.Deserialize>(@"{ ""Value"": """" }", _jsonOptions)); + } + + [Fact] + public void Deserialize_EmptyIReadOnlyList_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel + { + Value = [] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": """" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_String_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = ["a", "b", "c"] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_StringList_Valid_Success() + { + var desiredValue = new GenericBodyListModel + { + Value = ["a", "b", "c"] + }; + + Assert.Throws(() => JsonSerializer.Deserialize>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions)); + } + + [Fact] + public void Deserialize_String_Space_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = ["a", "b", "c"] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_GenericCommandType_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_GenericCommandType_EmptyEntry_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_GenericCommandType_Invalid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp,TotallyNotAValidCommand,MoveDown"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_GenericCommandType_Space_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_String_Array_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = ["a", "b", "c"] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Deserialize_GenericCommandType_Array_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel + { + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] + }; + + var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public void Serialize_GenericCommandType_ReadOnlyArray_Valid_Success() + { + var valueToSerialize = new GenericBodyIReadOnlyCollectionModel + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }.AsReadOnly() + }; + + string value = JsonSerializer.Serialize>(valueToSerialize, _jsonOptions); + Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value); + } + + [Fact] + public void Serialize_GenericCommandType_ImmutableArrayArray_Valid_Success() + { + var valueToSerialize = new GenericBodyIReadOnlyCollectionModel + { + Value = ImmutableArray.Create(new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }) + }; + + string value = JsonSerializer.Serialize>(valueToSerialize, _jsonOptions); + Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value); + } + + [Fact] + public void Serialize_GenericCommandType_List_Valid_Success() + { + var valueToSerialize = new GenericBodyIReadOnlyListModel + { + Value = new List { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + string value = JsonSerializer.Serialize>(valueToSerialize, _jsonOptions); + Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs index 9b977b9a5d..26989d59b2 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Extensions.Tests.Json.Models; @@ -87,5 +88,17 @@ namespace Jellyfin.Extensions.Tests.Json.Converters var value = JsonSerializer.Deserialize>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); Assert.Equal(desiredValue.Value, value?.Value); } + + [Fact] + public void Serialize_GenericCommandType_IReadOnlyList_Valid_Success() + { + var valueToSerialize = new GenericBodyIReadOnlyListModel + { + Value = new List { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + string value = JsonSerializer.Serialize>(valueToSerialize, _jsonOptions); + Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value); + } } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs index 76669ea19c..a698c9c92b 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Extensions.Tests.Json.Models /// Gets or sets the value. /// [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")] - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public T[] Value { get; set; } = default!; } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyCollectionModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyCollectionModel.cs new file mode 100644 index 0000000000..14cbc0f501 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyCollectionModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; + +namespace Jellyfin.Extensions.Tests.Json.Models +{ + /// + /// The generic body IReadOnlyCollection model. + /// + /// The value type. + public sealed class GenericBodyIReadOnlyCollectionModel + { + /// + /// Gets or sets the value. + /// + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public IReadOnlyCollection Value { get; set; } = default!; + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs index 7e6b97afe1..eaa06a5dd4 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs @@ -13,7 +13,7 @@ namespace Jellyfin.Extensions.Tests.Json.Models /// /// Gets or sets the value. /// - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList Value { get; set; } = default!; } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyListModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyListModel.cs new file mode 100644 index 0000000000..463f9922f6 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyListModel.cs @@ -0,0 +1,22 @@ +#pragma warning disable CA1002 // Do not expose generic lists +#pragma warning disable CA2227 // Collection properties should be read only + +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; + +namespace Jellyfin.Extensions.Tests.Json.Models +{ + /// + /// The generic body List model. + /// + /// The value type. + public sealed class GenericBodyListModel + { + /// + /// Gets or sets the value. + /// + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] + public List Value { get; set; } = default!; + } +} -- cgit v1.2.3