aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs4
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs57
-rw-r--r--tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs39
-rw-r--r--tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs54
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj13
-rw-r--r--tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs40
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj11
-rw-r--r--tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs88
-rw-r--r--tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs19
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj9
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj9
-rw-r--r--tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs (renamed from tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs)9
-rw-r--r--tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs59
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj35
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs)5
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs)4
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs (renamed from tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs)3
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs)4
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs (renamed from tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs (renamed from tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs)6
-rw-r--r--tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs (renamed from tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs)7
-rw-r--r--tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs41
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs36
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs24
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs8
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj13
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs138
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs12
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs83
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json144
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_year_only_metadata.json147
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json260
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/example2.srt11
-rw-r--r--tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs (renamed from tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs)68
-rw-r--r--tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs150
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj11
-rw-r--r--tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs159
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs27
-rw-r--r--tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj9
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs5
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs28
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs28
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs3
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs16
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs77
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs124
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StackTests.cs100
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs228
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs117
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj11
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs52
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj15
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs597
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs156
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs45
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs126
-rw-r--r--tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs (renamed from tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs)3
-rw-r--r--tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg0
-rw-r--r--tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg0
-rw-r--r--tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs13
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs103
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs6
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj9
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs17
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs232
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs32
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs63
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs240
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs179
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs124
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs164
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zipbin0 -> 162 bytes
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs63
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs30
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs4
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs134
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs61
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs117
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs12
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs17
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs1
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj15
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs15
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs32
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs12
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj15
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs95
-rw-r--r--tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs3
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs8
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs17
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo33
110 files changed, 4529 insertions, 978 deletions
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
index de03aa5f5..6f5c0ed0c 100644
--- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -132,11 +132,13 @@ namespace Jellyfin.Api.Tests.Auth
authorizationInfo.User.AddDefaultPreferences();
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
authorizationInfo.IsApiKey = false;
+ authorizationInfo.HasToken = true;
+ authorizationInfo.Token = "fake-token";
_jellyfinAuthServiceMock.Setup(
a => a.Authenticate(
It.IsAny<HttpRequest>()))
- .Returns(authorizationInfo);
+ .Returns(Task.FromResult(authorizationInfo));
return authorizationInfo;
}
diff --git a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
index a62fd8d5a..23c51999f 100644
--- a/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs
@@ -4,6 +4,7 @@ using AutoFixture;
using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Constants;
+using Jellyfin.Server.Implementations.Security;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
@@ -49,5 +50,61 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
await _sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
+
+ [Theory]
+ [MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
+ public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)
+ {
+ var dict = AuthorizationContext.GetParts(input);
+ foreach (var (key, value) in parts)
+ {
+ Assert.Equal(dict[key], value);
+ }
+ }
+
+ private static TheoryData<string, Dictionary<string, string>> GetParts_ValidAuthHeader_Success_Data()
+ {
+ var data = new TheoryData<string, Dictionary<string, string>>();
+
+ data.Add(
+ "x=\"123,123\",y=\"123\"",
+ new Dictionary<string, string>
+ {
+ { "x", "123,123" },
+ { "y", "123" }
+ });
+
+ data.Add(
+ "x=\"123,123\", y=\"123\",z=\"'hi'\"",
+ new Dictionary<string, string>
+ {
+ { "x", "123,123" },
+ { "y", "123" },
+ { "z", "'hi'" }
+ });
+
+ data.Add(
+ "x=\"ab\"",
+ new Dictionary<string, string>
+ {
+ { "x", "ab" }
+ });
+
+ data.Add(
+ "param=Hörbücher",
+ new Dictionary<string, string>
+ {
+ { "param", "Hörbücher" }
+ });
+
+ data.Add(
+ "param=%22%Hörbücher",
+ new Dictionary<string, string>
+ {
+ { "param", "\"%Hörbücher" }
+ });
+
+ return data;
+ }
}
}
diff --git a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs
index 117083815..1f06e8fde 100644
--- a/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Controllers/DynamicHlsControllerTests.cs
@@ -1,13 +1,5 @@
using System;
-using System.Collections.Generic;
-using AutoFixture;
-using AutoFixture.AutoMoq;
using Jellyfin.Api.Controllers;
-using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.StreamingDtos;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Controllers
@@ -26,33 +18,28 @@ namespace Jellyfin.Api.Tests.Controllers
}
}
- public static IEnumerable<object[]> GetSegmentLengths_Success_TestData()
+ public static TheoryData<long, int, double[]> GetSegmentLengths_Success_TestData()
{
- yield return new object[] { 0, 6, Array.Empty<double>() };
- yield return new object[]
- {
+ var data = new TheoryData<long, int, double[]>();
+ data.Add(0, 6, Array.Empty<double>());
+ data.Add(
TimeSpan.FromSeconds(3).Ticks,
6,
- new double[] { 3 }
- };
- yield return new object[]
- {
+ new double[] { 3 });
+ data.Add(
TimeSpan.FromSeconds(6).Ticks,
6,
- new double[] { 6 }
- };
- yield return new object[]
- {
+ new double[] { 6 });
+ data.Add(
TimeSpan.FromSeconds(3.3333333).Ticks,
6,
- new double[] { 3.3333333 }
- };
- yield return new object[]
- {
+ new double[] { 3.3333333 });
+ data.Add(
TimeSpan.FromSeconds(9.3333333).Ticks,
6,
- new double[] { 6, 3.3333333 }
- };
+ new double[] { 6, 3.3333333 });
+
+ return data;
}
}
}
diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
index 97e441b1d..c4640bd22 100644
--- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
+++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
@@ -15,16 +15,16 @@ namespace Jellyfin.Api.Tests.Helpers
Assert.Equal(expected, RequestHelpers.GetOrderBy(sortBy, requestedSortOrder));
}
- public static IEnumerable<object[]> GetOrderBy_Success_TestData()
+ public static TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]> GetOrderBy_Success_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]>();
+
+ data.Add(
Array.Empty<string>(),
Array.Empty<SortOrder>(),
- Array.Empty<(string, SortOrder)>()
- };
- yield return new object[]
- {
+ Array.Empty<(string, SortOrder)>());
+
+ data.Add(
new string[]
{
"IsFavoriteOrLiked",
@@ -35,10 +35,9 @@ namespace Jellyfin.Api.Tests.Helpers
{
("IsFavoriteOrLiked", SortOrder.Ascending),
("Random", SortOrder.Ascending),
- }
- };
- yield return new object[]
- {
+ });
+
+ data.Add(
new string[]
{
"SortName",
@@ -52,38 +51,9 @@ namespace Jellyfin.Api.Tests.Helpers
{
("SortName", SortOrder.Descending),
("ProductionYear", SortOrder.Descending),
- }
- };
- }
-
- [Fact]
- public static void GetItemTypeStrings_Empty_Empty()
- {
- Assert.Empty(RequestHelpers.GetItemTypeStrings(Array.Empty<BaseItemKind>()));
- }
-
- [Fact]
- public static void GetItemTypeStrings_Valid_Success()
- {
- BaseItemKind[] input =
- {
- BaseItemKind.AggregateFolder,
- BaseItemKind.Audio,
- BaseItemKind.BasePluginFolder,
- BaseItemKind.CollectionFolder
- };
-
- string[] expected =
- {
- "AggregateFolder",
- "Audio",
- "BasePluginFolder",
- "CollectionFolder"
- };
-
- var res = RequestHelpers.GetItemTypeStrings(input);
+ });
- Assert.Equal(expected, res);
+ return data;
}
}
}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index d4ea91872..bcbe9c1be 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -6,11 +6,8 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
@@ -18,12 +15,12 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.7" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
deleted file mode 100644
index 9903409fa..000000000
--- a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Common.Extensions;
-using Xunit;
-
-namespace Jellyfin.Common.Tests.Extensions
-{
- public static class CopyToExtensionsTests
- {
- public static IEnumerable<object[]> CopyTo_Valid_Correct_TestData()
- {
- yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } };
- yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } };
- }
-
- [Theory]
- [MemberData(nameof(CopyTo_Valid_Correct_TestData))]
- public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected)
- {
- source.CopyTo(destination, index);
- Assert.Equal(expected, destination);
- }
-
- public static IEnumerable<object[]> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
- {
- yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 };
- yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 };
- yield return new object[] { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 };
- yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 };
- yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 };
- }
-
- [Theory]
- [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
- public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index)
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index));
- }
- }
-}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 546b2487e..ce607b2ec 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -6,20 +6,17 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs
new file mode 100644
index 000000000..463e17ad3
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/BaseItemManagerTests.cs
@@ -0,0 +1,88 @@
+using System;
+using MediaBrowser.Controller.BaseItemManager;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Configuration;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests
+{
+ public class BaseItemManagerTests
+ {
+ [Theory]
+ [InlineData(typeof(Book), "LibraryEnabled", true)]
+ [InlineData(typeof(Book), "LibraryDisabled", false)]
+ [InlineData(typeof(MusicArtist), "Enabled", true)]
+ [InlineData(typeof(MusicArtist), "ServerDisabled", false)]
+ public void IsMetadataFetcherEnabled_ChecksOptions_ReturnsExpected(Type itemType, string fetcherName, bool expected)
+ {
+ BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
+
+ var libraryOptions = new LibraryOptions
+ {
+ TypeOptions = new[]
+ {
+ new TypeOptions
+ {
+ Type = "Book",
+ MetadataFetchers = new[] { "LibraryEnabled" }
+ }
+ }
+ };
+
+ var serverConfiguration = new ServerConfiguration();
+ foreach (var typeConfig in serverConfiguration.MetadataOptions)
+ {
+ typeConfig.DisabledMetadataFetchers = new[] { "ServerDisabled" };
+ }
+
+ var serverConfigurationManager = new Mock<IServerConfigurationManager>();
+ serverConfigurationManager.Setup(scm => scm.Configuration)
+ .Returns(serverConfiguration);
+
+ var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
+ var actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryOptions, fetcherName);
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Theory]
+ [InlineData(typeof(Book), "LibraryEnabled", true)]
+ [InlineData(typeof(Book), "LibraryDisabled", false)]
+ [InlineData(typeof(MusicArtist), "Enabled", true)]
+ [InlineData(typeof(MusicArtist), "ServerDisabled", false)]
+ public void IsImageFetcherEnabled_ChecksOptions_ReturnsExpected(Type itemType, string fetcherName, bool expected)
+ {
+ BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
+
+ var libraryOptions = new LibraryOptions
+ {
+ TypeOptions = new[]
+ {
+ new TypeOptions
+ {
+ Type = "Book",
+ ImageFetchers = new[] { "LibraryEnabled" }
+ }
+ }
+ };
+
+ var serverConfiguration = new ServerConfiguration();
+ foreach (var typeConfig in serverConfiguration.MetadataOptions)
+ {
+ typeConfig.DisabledImageFetchers = new[] { "ServerDisabled" };
+ }
+
+ var serverConfigurationManager = new Mock<IServerConfigurationManager>();
+ serverConfigurationManager.Setup(scm => scm.Configuration)
+ .Returns(serverConfiguration);
+
+ var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
+ var actual = baseItemManager.IsImageFetcherEnabled(item, libraryOptions, fetcherName);
+
+ Assert.Equal(expected, actual);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs
deleted file mode 100644
index 576c0a49b..000000000
--- a/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-using MediaBrowser.Controller.Extensions;
-using Xunit;
-
-namespace Jellyfin.Controller.Extensions.Tests
-{
- public class StringExtensionsTests
- {
- [Theory]
- [InlineData("", '_', 0)]
- [InlineData("___", '_', 3)]
- [InlineData("test\x00", '\x00', 1)]
- [InlineData("Imdb=tt0119567|Tmdb=330|TmdbCollection=328", '|', 2)]
- public void ReadOnlySpan_Count_Success(string str, char needle, int count)
- {
- Assert.Equal(count, str.AsSpan().Count(needle));
- }
- }
-}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 9a8ddafa0..0ffc19833 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -6,20 +6,17 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 1f6cd541c..098166001 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -1,20 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs
index 0adf098c3..7730841a1 100644
--- a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs
@@ -1,11 +1,10 @@
using System;
using System.Linq;
-using MediaBrowser.Controller.Sorting;
using Xunit;
-namespace Jellyfin.Controller.Tests
+namespace Jellyfin.Extensions.Tests
{
- public class AlphanumComparatorTests
+ public class AlphanumericComparatorTests
{
// InlineData is pre-sorted
[Theory]
@@ -20,10 +19,10 @@ namespace Jellyfin.Controller.Tests
[InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")]
[InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")]
[InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")]
- public void AlphanumComparatorTest(params string?[] strings)
+ public void AlphanumericComparatorTest(params string?[] strings)
{
var copy = strings.Reverse().ToArray();
- Array.Sort(copy, new AlphanumComparator());
+ Array.Sort(copy, new AlphanumericComparator());
Assert.True(strings.SequenceEqual(copy));
}
}
diff --git a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs
new file mode 100644
index 000000000..d46beedd9
--- /dev/null
+++ b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Jellyfin.Extensions.Tests
+{
+ public static class CopyToExtensionsTests
+ {
+ public static TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> CopyTo_Valid_Correct_TestData()
+ {
+ var data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>>();
+
+ data.Add(
+ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 });
+
+ data.Add(
+ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } );
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Valid_Correct_TestData))]
+ public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected)
+ {
+ source.CopyTo(destination, index);
+ Assert.Equal(expected, destination);
+ }
+
+ public static TheoryData<IReadOnlyList<int>, IList<int>, int> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
+ {
+ var data = new TheoryData<IReadOnlyList<int>, IList<int>, int>();
+
+ data.Add(
+ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 );
+
+ data.Add(
+ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 );
+
+ data.Add(
+ new[] { 0, 1, 2 }, Array.Empty<int>(), 0 );
+
+ data.Add(
+ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 );
+
+ data.Add(
+ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 );
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
+ public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
new file mode 100644
index 000000000..ee3af7559
--- /dev/null
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="coverlet.collector" Version="3.1.0">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
+ <ProjectReference Include="../../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs
index 7629d9912..125229ff9 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs
@@ -1,11 +1,10 @@
-using System.Globalization;
using System.Text.Json;
using FsCheck;
using FsCheck.Xunit;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonBoolNumberTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs
index ca300401d..f2ca2ff08 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs
@@ -1,11 +1,11 @@
-using System;
+using System;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Jellyfin.Common.Tests.Models;
+using Jellyfin.Extensions.Tests.Json.Models;
using MediaBrowser.Model.Session;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public static class JsonCommaDelimitedArrayTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs
index 34ad9bac7..92886dcd2 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs
@@ -1,10 +1,10 @@
-using System.Text.Json;
+using System.Text.Json;
using System.Text.Json.Serialization;
-using Jellyfin.Common.Tests.Models;
+using Jellyfin.Extensions.Tests.Json.Models;
using MediaBrowser.Model.Session;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public static class JsonCommaDelimitedIReadOnlyListTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs
index dbfad3c2f..8465d465a 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs
@@ -1,9 +1,9 @@
using System;
using System.Text.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonGuidConverterTests
{
diff --git a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs
index 955d296cc..af9227de2 100644
--- a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs
@@ -1,9 +1,10 @@
using System.Text.Json;
using System.Text.Json.Serialization;
+using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Model.Entities;
using Xunit;
-namespace Jellyfin.Model.Tests.Entities
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonLowerCaseConverterTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs
index cb3b66c4c..b0dbc09e4 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs
@@ -1,9 +1,9 @@
using System;
using System.Text.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonNullableGuidConverterTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
index 2b23c6705..655e07074 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
@@ -1,8 +1,8 @@
-using System.Text.Json;
-using MediaBrowser.Common.Json.Converters;
+using System.Text.Json;
+using Jellyfin.Extensions.Json.Converters;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonStringConverterTests
{
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs
index f2cefdbf8..5fbac7eab 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs
@@ -1,9 +1,9 @@
-using System;
+using System;
using System.Text.Json;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonVersionConverterTests
{
diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs
index 276e1bfbe..ef135278f 100644
--- a/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs
@@ -1,8 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
-namespace Jellyfin.Common.Tests.Models
+namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body model.
diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs
index 627454b25..8e7b5a35b 100644
--- a/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs
@@ -1,8 +1,8 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
+using Jellyfin.Extensions.Json.Converters;
-namespace Jellyfin.Common.Tests.Models
+namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body <c>IReadOnlyList</c> model.
diff --git a/tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs
index cbdbcf112..a73cfb078 100644
--- a/tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs
@@ -1,18 +1,15 @@
using System;
-using MediaBrowser.Common.Extensions;
using Xunit;
-namespace Jellyfin.Common.Tests.Extensions
+namespace Jellyfin.Extensions.Tests
{
public static class ShuffleExtensionsTests
{
- private static readonly Random _rng = new Random();
-
[Fact]
public static void Shuffle_Valid_Correct()
{
byte[] original = new byte[1 << 6];
- _rng.NextBytes(original);
+ Random.Shared.NextBytes(original);
byte[] shuffled = (byte[])original.Clone();
shuffled.Shuffle();
diff --git a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs
new file mode 100644
index 000000000..7186cc023
--- /dev/null
+++ b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs
@@ -0,0 +1,41 @@
+using System;
+using Xunit;
+
+namespace Jellyfin.Extensions.Tests
+{
+ public class StringExtensionsTests
+ {
+ [Theory]
+ [InlineData("", '_', 0)]
+ [InlineData("___", '_', 3)]
+ [InlineData("test\x00", '\x00', 1)]
+ [InlineData("Imdb=tt0119567|Tmdb=330|TmdbCollection=328", '|', 2)]
+ public void ReadOnlySpan_Count_Success(string str, char needle, int count)
+ {
+ Assert.Equal(count, str.AsSpan().Count(needle));
+ }
+
+ [Theory]
+ [InlineData("", 'q', "")]
+ [InlineData("Banana split", ' ', "Banana")]
+ [InlineData("Banana split", 'q', "Banana split")]
+ [InlineData("Banana split 2", ' ', "Banana")]
+ public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
+ {
+ var result = str.AsSpan().LeftPart(needle).ToString();
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("", 'q', "")]
+ [InlineData("Banana split", ' ', "split")]
+ [InlineData("Banana split", 'q', "Banana split")]
+ [InlineData("Banana split.", '.', "")]
+ [InlineData("Banana split 2", ' ', "2")]
+ public void RightPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
+ {
+ var result = str.AsSpan().RightPart(needle).ToString();
+ Assert.Equal(expectedResult, result);
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
index 39fd8afda..c0c363d3d 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections;
-using System.Collections.Generic;
using MediaBrowser.MediaEncoding.Encoder;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
@@ -9,15 +7,18 @@ namespace Jellyfin.MediaEncoding.Tests
{
public class EncoderValidatorTests
{
+ private readonly EncoderValidator _encoderValidator = new EncoderValidator(new NullLogger<EncoderValidatorTests>(), "ffmpeg");
+
[Theory]
[ClassData(typeof(GetFFmpegVersionTestData))]
public void GetFFmpegVersionTest(string versionOutput, Version? version)
{
- var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
- Assert.Equal(version, val.GetFFmpegVersion(versionOutput));
+ Assert.Equal(version, _encoderValidator.GetFFmpegVersionInternal(versionOutput));
}
[Theory]
+ [InlineData(EncoderValidatorTestsData.FFmpegV44Output, true)]
+ [InlineData(EncoderValidatorTestsData.FFmpegV432Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV431Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
@@ -28,25 +29,24 @@ namespace Jellyfin.MediaEncoding.Tests
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
public void ValidateVersionInternalTest(string versionOutput, bool valid)
{
- var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
- Assert.Equal(valid, val.ValidateVersionInternal(versionOutput));
+ Assert.Equal(valid, _encoderValidator.ValidateVersionInternal(versionOutput));
}
- private class GetFFmpegVersionTestData : IEnumerable<object?[]>
+ private class GetFFmpegVersionTestData : TheoryData<string, Version?>
{
- public IEnumerator<object?[]> GetEnumerator()
+ public GetFFmpegVersionTestData()
{
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput2, new Version(4, 0) };
- yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
+ Add(EncoderValidatorTestsData.FFmpegV44Output, new Version(4, 4));
+ Add(EncoderValidatorTestsData.FFmpegV432Output, new Version(4, 3, 2));
+ Add(EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1));
+ Add(EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3));
+ Add(EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1));
+ Add(EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2));
+ Add(EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4));
+ Add(EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4));
+ Add(EncoderValidatorTestsData.FFmpegGitUnknownOutput2, new Version(4, 0));
+ Add(EncoderValidatorTestsData.FFmpegGitUnknownOutput, null);
}
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index 9f5bef9a8..02bf046ed 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -2,6 +2,30 @@ namespace Jellyfin.MediaEncoding.Tests
{
internal static class EncoderValidatorTestsData
{
+ public const string FFmpegV44Output = @"ffmpeg version 4.4-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers
+built with gcc 10.3.0 (Rev5, Built by MSYS2 project)
+configuration: --disable-static --enable-shared --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls
+libavutil 56. 70.100 / 56. 70.100
+libavcodec 58.134.100 / 58.134.100
+libavformat 58. 76.100 / 58. 76.100
+libavdevice 58. 13.100 / 58. 13.100
+libavfilter 7.110.100 / 7.110.100
+libswscale 5. 9.100 / 5. 9.100
+libswresample 3. 9.100 / 3. 9.100
+libpostproc 55. 9.100 / 55. 9.100";
+
+ public const string FFmpegV432Output = @"ffmpeg version n4.3.2-Jellyfin Copyright (c) 2000-2021 the FFmpeg developers
+built with gcc 10.2.0 (Rev9, Built by MSYS2 project)
+configuration: --disable-static --enable-shared --cc='ccache gcc' --cxx='ccache g++' --extra-version=Jellyfin --disable-ffplay --disable-debug --enable-lto --enable-gpl --enable-version3 --enable-bzlib --enable-iconv --enable-lzma --enable-zlib --enable-sdl2 --enable-fontconfig --enable-gmp --enable-libass --enable-libzimg --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libdav1d --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libmfx --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-nvdec --enable-ffnvcodec --enable-gnutls
+libavutil 56. 51.100 / 56. 51.100
+libavcodec 58. 91.100 / 58. 91.100
+libavformat 58. 45.100 / 58. 45.100
+libavdevice 58. 10.100 / 58. 10.100
+libavfilter 7. 85.100 / 7. 85.100
+libswscale 5. 7.100 / 5. 7.100
+libswresample 3. 7.100 / 3. 7.100
+libpostproc 55. 7.100 / 55. 7.100";
+
public const string FFmpegV431Output = @"ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 10.1.0 (GCC)
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-avisynth --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
index 415682e85..97dbb3be0 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -1,8 +1,9 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.MediaEncoding.Probing;
+using MediaBrowser.Model.IO;
using Xunit;
namespace Jellyfin.MediaEncoding.Tests
@@ -14,9 +15,10 @@ namespace Jellyfin.MediaEncoding.Tests
public async Task Test(string fileName)
{
var path = Path.Join("Test Data", fileName);
- using (var stream = File.OpenRead(path))
+ await using (var stream = AsyncFile.OpenRead(path))
{
- await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.Options).ConfigureAwait(false);
+ var res = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.Options).ConfigureAwait(false);
+ Assert.NotNull(res);
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 6b828e113..dc4a42c19 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -6,11 +6,8 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
@@ -21,10 +18,14 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
index 98fbb00d5..0fc8724b6 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -2,11 +2,13 @@ using System;
using System.Globalization;
using System.IO;
using System.Text.Json;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using Xunit;
namespace Jellyfin.MediaEncoding.Tests.Probing
@@ -16,6 +18,19 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null);
+ [Theory]
+ [InlineData("2997/125", 23.976f)]
+ [InlineData("1/50", 0.02f)]
+ [InlineData("25/1", 25f)]
+ [InlineData("120/1", 120f)]
+ [InlineData("1704753000/71073479", 23.98578237601117f)]
+ [InlineData("0/0", null)]
+ [InlineData("1/1000", 0.001f)]
+ [InlineData("1/90000", 1.1111111E-05f)]
+ [InlineData("1/48000", 2.0833333E-05f)]
+ public void GetFrameRate_Success(string value, float? expected)
+ => Assert.Equal(expected, ProbeResultNormalizer.GetFrameRate(value));
+
[Fact]
public void GetMediaInfo_MetaData_Success()
{
@@ -56,6 +71,72 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
}
[Fact]
+ public void GetMediaInfo_Mp4MetaData_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/video_mp4_metadata.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+
+ // subtitle handling requires a localization object, set a mock to return the input string
+ var mockLocalization = new Mock<ILocalizationManager>();
+ mockLocalization.Setup(x => x.GetLocalizedString(It.IsAny<string>())).Returns<string>(x => x);
+ ProbeResultNormalizer localizedProbeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), mockLocalization.Object);
+
+ MediaInfo res = localizedProbeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_mp4_metadata.mkv", MediaProtocol.File);
+
+ // [Video, Audio (Main), Audio (Commentary), Subtitle (Main, Spanish), Subtitle (Main, English), Subtitle (Commentary)
+ Assert.Equal(6, res.MediaStreams.Count);
+
+ Assert.NotNull(res.VideoStream);
+ Assert.Equal(res.MediaStreams[0], res.VideoStream);
+ Assert.Equal(0, res.VideoStream.Index);
+ Assert.Equal("h264", res.VideoStream.Codec);
+ Assert.Equal("High", res.VideoStream.Profile);
+ Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
+ Assert.Equal(358, res.VideoStream.Height);
+ Assert.Equal(720, res.VideoStream.Width);
+ Assert.Equal("2.40:1", res.VideoStream.AspectRatio);
+ Assert.Equal("yuv420p", res.VideoStream.PixelFormat);
+ Assert.Equal(31d, res.VideoStream.Level);
+ Assert.Equal(1, res.VideoStream.RefFrames);
+ Assert.True(res.VideoStream.IsAVC);
+ Assert.Equal(120f, res.VideoStream.RealFrameRate);
+ Assert.Equal("1/90000", res.VideoStream.TimeBase);
+ Assert.Equal(1147365, res.VideoStream.BitRate);
+ Assert.Equal(8, res.VideoStream.BitDepth);
+ Assert.True(res.VideoStream.IsDefault);
+ Assert.Equal("und", res.VideoStream.Language);
+
+ Assert.Equal(MediaStreamType.Audio, res.MediaStreams[1].Type);
+ Assert.Equal("aac", res.MediaStreams[1].Codec);
+ Assert.Equal(7, res.MediaStreams[1].Channels);
+ Assert.True(res.MediaStreams[1].IsDefault);
+ Assert.Equal("eng", res.MediaStreams[1].Language);
+ Assert.Equal("Surround 6.1", res.MediaStreams[1].Title);
+
+ Assert.Equal(MediaStreamType.Audio, res.MediaStreams[2].Type);
+ Assert.Equal("aac", res.MediaStreams[2].Codec);
+ Assert.Equal(2, res.MediaStreams[2].Channels);
+ Assert.False(res.MediaStreams[2].IsDefault);
+ Assert.Equal("eng", res.MediaStreams[2].Language);
+ Assert.Equal("Commentary", res.MediaStreams[2].Title);
+
+ Assert.Equal("spa", res.MediaStreams[3].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[3].Type);
+ Assert.Equal("DVDSUB", res.MediaStreams[3].Codec);
+ Assert.Null(res.MediaStreams[3].Title);
+
+ Assert.Equal("eng", res.MediaStreams[4].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[4].Type);
+ Assert.Equal("mov_text", res.MediaStreams[4].Codec);
+ Assert.Null(res.MediaStreams[4].Title);
+
+ Assert.Equal("eng", res.MediaStreams[5].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[5].Type);
+ Assert.Equal("mov_text", res.MediaStreams[5].Codec);
+ Assert.Equal("Commentary", res.MediaStreams[5].Title);
+ }
+
+ [Fact]
public void GetMediaInfo_MusicVideo_Success()
{
var bytes = File.ReadAllBytes("Test Data/Probing/music_video_metadata.json");
@@ -69,7 +150,60 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
Assert.Equal("Album", res.Album);
Assert.Equal(2021, res.ProductionYear);
Assert.True(res.PremiereDate.HasValue);
- Assert.Equal(DateTime.Parse("2021-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo).ToUniversalTime(), res.PremiereDate);
+ Assert.Equal(DateTime.Parse("2021-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate);
+ }
+
+ [Fact]
+ public void GetMediaInfo_GivenOriginalDateContainsOnlyYear_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/music_year_only_metadata.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+ MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, null, true, "Test Data/Probing/music.flac", MediaProtocol.File);
+
+ Assert.Equal("Baker Street", res.Name);
+ Assert.Single(res.Artists);
+ Assert.Equal("Gerry Rafferty", res.Artists[0]);
+ Assert.Equal("City to City", res.Album);
+ Assert.Equal(1978, res.ProductionYear);
+ Assert.True(res.PremiereDate.HasValue);
+ Assert.Equal(DateTime.Parse("1978-01-01T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate);
+ Assert.Contains("Electronic", res.Genres);
+ Assert.Contains("Ambient", res.Genres);
+ Assert.Contains("Pop", res.Genres);
+ Assert.Contains("Jazz", res.Genres);
+ }
+
+ [Fact]
+ public void GetMediaInfo_Music_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/music_metadata.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+ MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, null, true, "Test Data/Probing/music.flac", MediaProtocol.File);
+
+ Assert.Equal("UP NO MORE", res.Name);
+ Assert.Single(res.Artists);
+ Assert.Equal("TWICE", res.Artists[0]);
+ Assert.Equal("Eyes wide open", res.Album);
+ Assert.Equal(2020, res.ProductionYear);
+ Assert.True(res.PremiereDate.HasValue);
+ Assert.Equal(DateTime.Parse("2020-10-26T00:00Z", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AdjustToUniversal), res.PremiereDate);
+ Assert.Equal(22, res.People.Length);
+ Assert.Equal("Krysta Youngs", res.People[0].Name);
+ Assert.Equal(PersonType.Composer, res.People[0].Type);
+ Assert.Equal("Julia Ross", res.People[1].Name);
+ Assert.Equal(PersonType.Composer, res.People[1].Type);
+ Assert.Equal("Yiwoomin", res.People[2].Name);
+ Assert.Equal(PersonType.Composer, res.People[2].Type);
+ Assert.Equal("Ji-hyo Park", res.People[3].Name);
+ Assert.Equal(PersonType.Lyricist, res.People[3].Type);
+ Assert.Equal("Yiwoomin", res.People[4].Name);
+ Assert.Equal(PersonType.Actor, res.People[4].Type);
+ Assert.Equal("Electric Piano", res.People[4].Role);
+ Assert.Equal(4, res.Genres.Length);
+ Assert.Contains("Electronic", res.Genres);
+ Assert.Contains("Trance", res.Genres);
+ Assert.Contains("Dance", res.Genres);
+ Assert.Contains("Jazz", res.Genres);
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
index 537a944b0..c07c9ea7d 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -31,5 +31,27 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
Assert.Equal("Very good, Lieutenant.", trackEvent2.Text);
}
}
+
+ [Fact]
+ public void Parse_EmptyNewlineBetweenText_Success()
+ {
+ using (var stream = File.OpenRead("Test Data/example2.srt"))
+ {
+ var parsed = new SrtParser(new NullLogger<SrtParser>()).Parse(stream, CancellationToken.None);
+ Assert.Equal(2, parsed.TrackEvents.Count);
+
+ var trackEvent1 = parsed.TrackEvents[0];
+ Assert.Equal("311", trackEvent1.Id);
+ Assert.Equal(TimeSpan.Parse("00:16:46.465", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:16:49.009", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
+ Assert.Equal("Una vez que la gente se entere" + Environment.NewLine + Environment.NewLine + "de que ustedes están aquí,", trackEvent1.Text);
+
+ var trackEvent2 = parsed.TrackEvents[1];
+ Assert.Equal("312", trackEvent2.Id);
+ Assert.Equal(TimeSpan.Parse("00:16:49.092", CultureInfo.InvariantCulture).Ticks, trackEvent2.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:16:51.470", CultureInfo.InvariantCulture).Ticks, trackEvent2.EndPositionTicks);
+ Assert.Equal("este lugar se convertirá" + Environment.NewLine + Environment.NewLine + "en un maldito zoológico.", trackEvent2.Text);
+ }
+ }
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
index 5db80c300..56649db8f 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
@@ -38,10 +38,11 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
}
}
- public static IEnumerable<object[]> Parse_MultipleDialogues_TestData()
+ public static TheoryData<string, IReadOnlyList<SubtitleTrackEvent>> Parse_MultipleDialogues_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, IReadOnlyList<SubtitleTrackEvent>>();
+
+ data.Add(
@"[Events]
Format: Layer, Start, End, Text
Dialogue: ,0:00:01.18,0:00:01.85,dialogue1
@@ -65,8 +66,9 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
StartPositionTicks = 31800000,
EndPositionTicks = 38500000
}
- }
- };
+ });
+
+ return data;
}
[Fact]
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs
new file mode 100644
index 000000000..639c364df
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SubtitleEncoderTests.cs
@@ -0,0 +1,83 @@
+using System.Threading;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
+{
+ public class SubtitleEncoderTests
+ {
+ internal static TheoryData<MediaSourceInfo, MediaStream, SubtitleEncoder.SubtitleInfo> GetReadableFile_Valid_TestData()
+ {
+ var data = new TheoryData<MediaSourceInfo, MediaStream, SubtitleEncoder.SubtitleInfo>();
+
+ data.Add(
+ new MediaSourceInfo()
+ {
+ Protocol = MediaProtocol.File
+ },
+ new MediaStream()
+ {
+ Path = "/media/sub.ass",
+ IsExternal = true
+ },
+ new SubtitleEncoder.SubtitleInfo("/media/sub.ass", MediaProtocol.File, "ass", true));
+
+ data.Add(
+ new MediaSourceInfo()
+ {
+ Protocol = MediaProtocol.File
+ },
+ new MediaStream()
+ {
+ Path = "/media/sub.ssa",
+ IsExternal = true
+ },
+ new SubtitleEncoder.SubtitleInfo("/media/sub.ssa", MediaProtocol.File, "ssa", true));
+
+ data.Add(
+ new MediaSourceInfo()
+ {
+ Protocol = MediaProtocol.File
+ },
+ new MediaStream()
+ {
+ Path = "/media/sub.srt",
+ IsExternal = true
+ },
+ new SubtitleEncoder.SubtitleInfo("/media/sub.srt", MediaProtocol.File, "srt", true));
+
+ data.Add(
+ new MediaSourceInfo()
+ {
+ Protocol = MediaProtocol.Http
+ },
+ new MediaStream()
+ {
+ Path = "/media/sub.ass",
+ IsExternal = true
+ },
+ new SubtitleEncoder.SubtitleInfo("/media/sub.ass", MediaProtocol.File, "ass", true));
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(GetReadableFile_Valid_TestData))]
+ internal async Task GetReadableFile_Valid_Success(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleEncoder.SubtitleInfo subtitleInfo)
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ var subtitleEncoder = fixture.Create<SubtitleEncoder>();
+ var result = await subtitleEncoder.GetReadableFile(mediaSource, subtitleStream, CancellationToken.None).ConfigureAwait(false);
+ Assert.Equal(subtitleInfo.Path, result.Path);
+ Assert.Equal(subtitleInfo.Protocol, result.Protocol);
+ Assert.Equal(subtitleInfo.Format, result.Format);
+ Assert.Equal(subtitleInfo.IsExternal, result.IsExternal);
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json
new file mode 100644
index 000000000..6530629fe
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_metadata.json
@@ -0,0 +1,144 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "flac",
+ "codec_long_name": "FLAC (Free Lossless Audio Codec)",
+ "codec_type": "audio",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "sample_fmt": "s16",
+ "sample_rate": "44100",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/44100",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 9447984,
+ "duration": "214.240000",
+ "bits_per_raw_sample": "16",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "mjpeg",
+ "codec_long_name": "Motion JPEG",
+ "profile": "Baseline",
+ "codec_type": "video",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "width": 500,
+ "height": 500,
+ "coded_width": 500,
+ "coded_height": 500,
+ "closed_captions": 0,
+ "has_b_frames": 0,
+ "sample_aspect_ratio": "1:1",
+ "display_aspect_ratio": "1:1",
+ "pix_fmt": "yuvj420p",
+ "level": -99,
+ "color_range": "pc",
+ "color_space": "bt470bg",
+ "chroma_location": "center",
+ "refs": 1,
+ "r_frame_rate": "90000/1",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 19281600,
+ "duration": "214.240000",
+ "bits_per_raw_sample": "8",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 1,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "comment": "Cover (front)"
+ }
+ }
+ ],
+ "format": {
+ "filename": "03 UP NO MORE.flac",
+ "nb_streams": 2,
+ "nb_programs": 0,
+ "format_name": "flac",
+ "format_long_name": "raw FLAC",
+ "start_time": "0.000000",
+ "duration": "214.240000",
+ "size": "28714641",
+ "bit_rate": "1072242",
+ "probe_score": 100,
+ "tags": {
+ "MUSICBRAINZ_RELEASEGROUPID": "aa05ff10-8589-4c9c-a0d4-6b024f4e4556",
+ "ORIGINALDATE": "2020-10-26",
+ "ORIGINALYEAR": "2020",
+ "RELEASETYPE": "album",
+ "MUSICBRAINZ_ALBUMID": "222e6610-75c9-400e-8dc3-bb61f9fc5ca7",
+ "SCRIPT": "Latn",
+ "ALBUM": "Eyes wide open",
+ "RELEASECOUNTRY": "JP",
+ "BARCODE": "190295105280",
+ "LABEL": "JYP Entertainment",
+ "RELEASESTATUS": "official",
+ "DATE": "2020-10-26",
+ "MUSICBRAINZ_ALBUMARTISTID": "8da127cc-c432-418f-b356-ef36210d82ac",
+ "album_artist": "TWICE",
+ "ALBUMARTISTSORT": "TWICE",
+ "TOTALDISCS": "1",
+ "TOTALTRACKS": "13",
+ "MEDIA": "Digital Media",
+ "disc": "1",
+ "MUSICBRAINZ_TRACKID": "7d1a1044-b564-480d-9df3-22f9656fdb97",
+ "TITLE": "UP NO MORE",
+ "ISRC": "US5TA2000136",
+ "PERFORMER": "Yiwoomin (electric piano);Yiwoomin (synthesizer);Yiwoomin (bass);Yiwoomin (guitar);TWICE;Tzu-yu Chou (vocals);Momo Hirai (vocals);Na-yeon Im (vocals);Da-hyun Kim (vocals);Sana Minatozaki (vocals);Mina Myoui (vocals);Ji-hyo Park (vocals);Chae-young Son (vocals);Jeong-yeon Yoo (vocals);Perrie (background vocals)",
+ "MIXER": "Bong Won Shin",
+ "ARRANGER": "Krysta Youngs;Julia Ross;Yiwoomin",
+ "MUSICBRAINZ_WORKID": "02b37083-0337-4721-9f17-bf31971043e8",
+ "LANGUAGE": "kor;eng",
+ "WORK": "Up No More",
+ "COMPOSER": "Krysta Youngs;Julia Ross;Yiwoomin",
+ "COMPOSERSORT": "Krysta Youngs;Ross, Julia;Yiwoomin",
+ "LYRICIST": "Ji-hyo Park",
+ "MUSICBRAINZ_ARTISTID": "8da127cc-c432-418f-b356-ef36210d82ac",
+ "ARTIST": "TWICE",
+ "ARTISTSORT": "TWICE",
+ "ARTISTS": "TWICE",
+ "MUSICBRAINZ_RELEASETRACKID": "ad49b840-da9e-4e7c-924b-29fdee187052",
+ "track": "3",
+ "GENRE": "Electronic;Trance;Dance;Jazz",
+ "WEBSITE": "http://twice.jype.com/;http://www.twicejapan.com/",
+ "ACOUSTID_ID": "aae2e972-108c-4d0c-8e31-9d078283e3dc",
+ "MOOD": "Not acoustic;Not aggressive;Electronic;Happy;Party;Not relaxed;Not sad",
+ "TRACKTOTAL": "13",
+ "DISCTOTAL": "1"
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_year_only_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_year_only_metadata.json
new file mode 100644
index 000000000..ddf890c45
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/music_year_only_metadata.json
@@ -0,0 +1,147 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "flac",
+ "codec_long_name": "FLAC (Free Lossless Audio Codec)",
+ "codec_type": "audio",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "sample_fmt": "s16",
+ "sample_rate": "44100",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/44100",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 16394616,
+ "duration": "371.760000",
+ "bits_per_raw_sample": "16",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "mjpeg",
+ "codec_long_name": "Motion JPEG",
+ "profile": "Baseline",
+ "codec_type": "video",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "width": 500,
+ "height": 498,
+ "coded_width": 500,
+ "coded_height": 498,
+ "closed_captions": 0,
+ "has_b_frames": 0,
+ "sample_aspect_ratio": "1:1",
+ "display_aspect_ratio": "250:249",
+ "pix_fmt": "yuvj420p",
+ "level": -99,
+ "color_range": "pc",
+ "color_space": "bt470bg",
+ "chroma_location": "center",
+ "refs": 1,
+ "r_frame_rate": "90000/1",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 33458400,
+ "duration": "371.760000",
+ "bits_per_raw_sample": "8",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 1,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "comment": "Cover (front)"
+ }
+ }
+ ],
+ "format": {
+ "filename": "02 Baker Street.flac",
+ "nb_streams": 2,
+ "nb_programs": 0,
+ "format_name": "flac",
+ "format_long_name": "raw FLAC",
+ "start_time": "0.000000",
+ "duration": "371.760000",
+ "size": "37072649",
+ "bit_rate": "797775",
+ "probe_score": 100,
+ "tags": {
+ "MUSICBRAINZ_RELEASEGROUPID": "238c3fb4-5792-342b-b217-02f66298b424",
+ "ORIGINALDATE": "1978",
+ "ORIGINALYEAR": "1978",
+ "RELEASETYPE": "album",
+ "MUSICBRAINZ_ALBUMID": "30156786-e511-3106-ac95-66f0e880b24b",
+ "ASIN": "B000007O5H",
+ "MUSICBRAINZ_ALBUMARTISTID": "563201cb-721c-4cfb-acca-c1ba69e3d1fb",
+ "album_artist": "Gerry Rafferty",
+ "ALBUMARTISTSORT": "Rafferty, Gerry",
+ "LABEL": "Liberty EMI Records UK",
+ "CATALOGNUMBER": "CDP 7 46049 2",
+ "DATE": "1989-07-26",
+ "RELEASECOUNTRY": "GB",
+ "BARCODE": "077774604925",
+ "ALBUM": "City to City",
+ "SCRIPT": "Latn",
+ "RELEASESTATUS": "official",
+ "TOTALDISCS": "1",
+ "disc": "1",
+ "MEDIA": "CD",
+ "TOTALTRACKS": "10",
+ "MUSICBRAINZ_TRACKID": "9235e22e-afbd-48f7-b329-21dae6da2810",
+ "ISRC": "GBAYE1100924;GBAYE7800619",
+ "PERFORMER": "Hugh Burns (electric guitar);Nigel Jenkins (electric guitar);Tommy Eyre (keyboard and Moog);Glen LeFleur (percussion);Raphael Ravenscroft (saxophone);Henry Spinetti (drums (drum set));Gary Taylor (bass);Gerry Rafferty (lead vocals)",
+ "ARRANGER": "Graham Preskett",
+ "MIXER": "Declan O’Doherty",
+ "PRODUCER": "Hugh Murphy;Gerry Rafferty",
+ "MUSICBRAINZ_WORKID": "a9eb3c45-784c-3c32-860c-4b406f03961b",
+ "LANGUAGE": "eng",
+ "WORK": "Baker Street",
+ "COMPOSER": "Gerry Rafferty",
+ "COMPOSERSORT": "Rafferty, Gerry",
+ "LYRICIST": "Gerry Rafferty",
+ "TITLE": "Baker Street",
+ "MUSICBRAINZ_ARTISTID": "563201cb-721c-4cfb-acca-c1ba69e3d1fb",
+ "ARTIST": "Gerry Rafferty",
+ "ARTISTSORT": "Rafferty, Gerry",
+ "ARTISTS": "Gerry Rafferty",
+ "MUSICBRAINZ_RELEASETRACKID": "407cf7f7-440d-3e76-8b89-8686198868ea",
+ "track": "2",
+ "GENRE": "Electronic;Ambient;Pop;Jazz",
+ "WEBSITE": "http://www.gerryrafferty.com/",
+ "ACOUSTID_ID": "68f8d979-a659-4aa0-a216-eb3721a951eb",
+ "MOOD": "Acoustic;Not aggressive;Not electronic;Not happy;Party;Relaxed;Not sad",
+ "TRACKTOTAL": "10",
+ "DISCTOTAL": "1"
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json
new file mode 100644
index 000000000..77e3def76
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json
@@ -0,0 +1,260 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "High",
+ "codec_type": "video",
+ "codec_tag_string": "avc1",
+ "codec_tag": "0x31637661",
+ "width": 720,
+ "height": 358,
+ "coded_width": 720,
+ "coded_height": 358,
+ "closed_captions": 0,
+ "has_b_frames": 2,
+ "sample_aspect_ratio": "32:27",
+ "display_aspect_ratio": "1280:537",
+ "pix_fmt": "yuv420p",
+ "level": 31,
+ "color_range": "tv",
+ "color_space": "smpte170m",
+ "color_transfer": "bt709",
+ "color_primaries": "smpte170m",
+ "chroma_location": "left",
+ "refs": 1,
+ "is_avc": "true",
+ "nal_length_size": "4",
+ "r_frame_rate": "120/1",
+ "avg_frame_rate": "1704753000/71073479",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1421469580,
+ "duration": "15794.106444",
+ "bit_rate": "1147365",
+ "bits_per_raw_sample": "8",
+ "nb_frames": "378834",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "und",
+ "handler_name": "VideoHandler",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_tag_string": "mp4a",
+ "codec_tag": "0x6134706d",
+ "sample_fmt": "fltp",
+ "sample_rate": "48000",
+ "channels": 7,
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/48000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 758115312,
+ "duration": "15794.069000",
+ "bit_rate": "224197",
+ "nb_frames": "740348",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Surround 6.1",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 2,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_tag_string": "mp4a",
+ "codec_tag": "0x6134706d",
+ "sample_fmt": "fltp",
+ "sample_rate": "48000",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/48000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 758114304,
+ "duration": "15794.048000",
+ "bit_rate": "160519",
+ "nb_frames": "740347",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Commentary",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 3,
+ "codec_name": "dvd_subtitle",
+ "codec_long_name": "DVD subtitles",
+ "codec_type": "subtitle",
+ "codec_tag_string": "mp4s",
+ "codec_tag": "0x7334706d",
+ "width": 720,
+ "height": 480,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1300301588,
+ "duration": "14447.795422",
+ "bit_rate": "2653",
+ "nb_frames": "3545",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "spa",
+ "handler_name": "SubtitleHandler"
+ }
+ },
+ {
+ "index": 4,
+ "codec_name": "mov_text",
+ "codec_long_name": "MOV text",
+ "codec_type": "subtitle",
+ "codec_tag_string": "tx3g",
+ "codec_tag": "0x67337874",
+ "width": 853,
+ "height": 51,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1401339330,
+ "duration": "15570.437000",
+ "bit_rate": "88",
+ "nb_frames": "5079",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "SubtitleHandler"
+ }
+ },
+ {
+ "index": 5,
+ "codec_name": "mov_text",
+ "codec_long_name": "MOV text",
+ "codec_type": "subtitle",
+ "codec_tag_string": "tx3g",
+ "codec_tag": "0x67337874",
+ "width": 853,
+ "height": 51,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1370580300,
+ "duration": "15228.670000",
+ "bit_rate": "18",
+ "nb_frames": "1563",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Commentary"
+ }
+ }
+ ]
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/example2.srt b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example2.srt
new file mode 100644
index 000000000..b14aa8ea3
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example2.srt
@@ -0,0 +1,11 @@
+311
+00:16:46,465 --> 00:16:49,009
+Una vez que la gente se entere
+
+de que ustedes están aquí,
+
+312
+00:16:49,092 --> 00:16:51,470
+este lugar se convertirá
+
+en un maldito zoológico.
diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs
index e6c325bac..6948280a3 100644
--- a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
+++ b/tests/Jellyfin.Model.Tests/Cryptography/PasswordHashTests.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
-using MediaBrowser.Common.Cryptography;
+using MediaBrowser.Model.Cryptography;
using Xunit;
-namespace Jellyfin.Common.Tests.Cryptography
+namespace Jellyfin.Model.Tests.Cryptography
{
public static class PasswordHashTests
{
@@ -19,18 +19,16 @@ namespace Jellyfin.Common.Tests.Cryptography
Assert.Throws<ArgumentException>(() => new PasswordHash(string.Empty, Array.Empty<byte>()));
}
- public static IEnumerable<object[]> Parse_Valid_TestData()
+ public static TheoryData<string, PasswordHash> Parse_Valid_TestData()
{
+ var data = new TheoryData<string, PasswordHash>();
// Id
- yield return new object[]
- {
+ data.Add(
"$PBKDF2",
- new PasswordHash("PBKDF2", Array.Empty<byte>())
- };
+ new PasswordHash("PBKDF2", Array.Empty<byte>()));
// Id + parameter
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$iterations=1000",
new PasswordHash(
"PBKDF2",
@@ -39,12 +37,10 @@ namespace Jellyfin.Common.Tests.Cryptography
new Dictionary<string, string>()
{
{ "iterations", "1000" },
- })
- };
+ }));
// Id + parameters
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$iterations=1000,m=120",
new PasswordHash(
"PBKDF2",
@@ -54,34 +50,28 @@ namespace Jellyfin.Common.Tests.Cryptography
{
{ "iterations", "1000" },
{ "m", "120" }
- })
- };
+ }));
// Id + hash
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
new PasswordHash(
"PBKDF2",
Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
Array.Empty<byte>(),
- new Dictionary<string, string>())
- };
+ new Dictionary<string, string>()));
// Id + salt + hash
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
new PasswordHash(
"PBKDF2",
Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
Convert.FromHexString("69F420"),
- new Dictionary<string, string>())
- };
+ new Dictionary<string, string>()));
// Id + parameter + hash
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
new PasswordHash(
"PBKDF2",
@@ -90,12 +80,9 @@ namespace Jellyfin.Common.Tests.Cryptography
new Dictionary<string, string>()
{
{ "iterations", "1000" }
- })
- };
-
+ }));
// Id + parameters + hash
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
new PasswordHash(
"PBKDF2",
@@ -105,12 +92,9 @@ namespace Jellyfin.Common.Tests.Cryptography
{
{ "iterations", "1000" },
{ "m", "120" }
- })
- };
-
+ }));
// Id + parameters + salt + hash
- yield return new object[]
- {
+ data.Add(
"$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
new PasswordHash(
"PBKDF2",
@@ -120,8 +104,8 @@ namespace Jellyfin.Common.Tests.Cryptography
{
{ "iterations", "1000" },
{ "m", "120" }
- })
- };
+ }));
+ return data;
}
[Theory]
@@ -171,11 +155,11 @@ namespace Jellyfin.Common.Tests.Cryptography
[InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
[InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
[InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
- [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
- [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
- [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
- [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
- [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash
+ [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
+ [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
+ [InlineData("$PBKDF2$iterations=1000$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
+ [InlineData("$PBKDF2$iterations=1000$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
+ [InlineData("$PBKDF2$iterations=1000$69F420$invalid hash")] // Invalid hash
[InlineData("$PBKDF2$69F420$")] // Empty hash
public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
{
diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs
new file mode 100644
index 000000000..0c97a90b4
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs
@@ -0,0 +1,150 @@
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Entities
+{
+ public class MediaStreamTests
+ {
+ public static TheoryData<MediaStream, string> Get_DisplayTitle_TestData()
+ {
+ var data = new TheoryData<MediaStream, string>();
+
+ data.Add(
+ new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Title = "English",
+ Language = string.Empty,
+ IsForced = false,
+ IsDefault = false,
+ Codec = "ASS"
+ },
+ "English - Und - ASS");
+
+ data.Add(
+ new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Title = "English",
+ Language = string.Empty,
+ IsForced = false,
+ IsDefault = false,
+ Codec = string.Empty
+ },
+ "English - Und");
+
+ data.Add(
+ new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Title = "English",
+ Language = "EN",
+ IsForced = false,
+ IsDefault = false,
+ Codec = string.Empty
+ },
+ "English");
+
+ data.Add(
+ new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Title = "English",
+ Language = "EN",
+ IsForced = true,
+ IsDefault = true,
+ Codec = "SRT"
+ },
+ "English - Default - Forced - SRT");
+
+ data.Add(
+ new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Title = null,
+ Language = null,
+ IsForced = false,
+ IsDefault = false,
+ Codec = null
+ },
+ "Und");
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(Get_DisplayTitle_TestData))]
+ public void Get_DisplayTitle_should_return_valid_title(MediaStream mediaStream, string expected)
+ {
+ Assert.Equal(expected, mediaStream.DisplayTitle);
+ }
+
+ [Theory]
+ [InlineData(null, null, false, null)]
+ [InlineData(null, 0, false, null)]
+ [InlineData(0, null, false, null)]
+ [InlineData(640, 480, false, "480p")]
+ [InlineData(640, 480, true, "480i")]
+ [InlineData(720, 576, false, "576p")]
+ [InlineData(720, 576, true, "576i")]
+ [InlineData(960, 540, false, "540p")]
+ [InlineData(960, 540, true, "540i")]
+ [InlineData(1280, 720, false, "720p")]
+ [InlineData(1280, 720, true, "720i")]
+ [InlineData(1920, 1080, false, "1080p")]
+ [InlineData(1920, 1080, true, "1080i")]
+ [InlineData(4096, 3072, false, "4K")]
+ [InlineData(8192, 6144, false, "8K")]
+ [InlineData(512, 384, false, "480p")]
+ [InlineData(576, 336, false, "480p")]
+ [InlineData(624, 352, false, "480p")]
+ [InlineData(640, 352, false, "480p")]
+ [InlineData(704, 396, false, "480p")]
+ [InlineData(720, 404, false, "480p")]
+ [InlineData(720, 480, false, "480p")]
+ [InlineData(768, 576, false, "576p")]
+ [InlineData(960, 720, false, "720p")]
+ [InlineData(1280, 528, false, "720p")]
+ [InlineData(1280, 532, false, "720p")]
+ [InlineData(1280, 534, false, "720p")]
+ [InlineData(1280, 536, false, "720p")]
+ [InlineData(1280, 544, false, "720p")]
+ [InlineData(1280, 690, false, "720p")]
+ [InlineData(1280, 694, false, "720p")]
+ [InlineData(1280, 696, false, "720p")]
+ [InlineData(1280, 716, false, "720p")]
+ [InlineData(1280, 718, false, "720p")]
+ [InlineData(1912, 792, false, "1080p")]
+ [InlineData(1916, 1076, false, "1080p")]
+ [InlineData(1918, 1080, false, "1080p")]
+ [InlineData(1920, 796, false, "1080p")]
+ [InlineData(1920, 800, false, "1080p")]
+ [InlineData(1920, 802, false, "1080p")]
+ [InlineData(1920, 804, false, "1080p")]
+ [InlineData(1920, 808, false, "1080p")]
+ [InlineData(1920, 816, false, "1080p")]
+ [InlineData(1920, 856, false, "1080p")]
+ [InlineData(1920, 960, false, "1080p")]
+ [InlineData(1920, 1024, false, "1080p")]
+ [InlineData(1920, 1040, false, "1080p")]
+ [InlineData(1920, 1072, false, "1080p")]
+ [InlineData(1440, 1072, false, "1080p")]
+ [InlineData(1440, 1080, false, "1080p")]
+ [InlineData(3840, 1600, false, "4K")]
+ [InlineData(3840, 1606, false, "4K")]
+ [InlineData(3840, 1608, false, "4K")]
+ [InlineData(3840, 2160, false, "4K")]
+ [InlineData(7680, 4320, false, "8K")]
+ public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string expected)
+ {
+ var mediaStream = new MediaStream()
+ {
+ Width = width,
+ Height = height,
+ IsInterlaced = interlaced
+ };
+
+ Assert.Equal(expected, mediaStream.GetResolutionText());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 40c51e524..7e8397d9f 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -1,20 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
new file mode 100644
index 000000000..cbab455f0
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
@@ -0,0 +1,159 @@
+using MediaBrowser.Model.Net;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Net
+{
+ public class MimeTypesTests
+ {
+ [Theory]
+ [InlineData(".dll", "application/octet-stream")]
+ [InlineData(".log", "text/plain")]
+ [InlineData(".srt", "application/x-subrip")]
+ [InlineData(".html", "text/html; charset=UTF-8")]
+ [InlineData(".htm", "text/html; charset=UTF-8")]
+ [InlineData(".7z", "application/x-7z-compressed")]
+ [InlineData(".azw", "application/vnd.amazon.ebook")]
+ [InlineData(".azw3", "application/vnd.amazon.ebook")]
+ [InlineData(".eot", "application/vnd.ms-fontobject")]
+ [InlineData(".epub", "application/epub+zip")]
+ [InlineData(".json", "application/json")]
+ [InlineData(".mobi", "application/x-mobipocket-ebook")]
+ [InlineData(".opf", "application/oebps-package+xml")]
+ [InlineData(".pdf", "application/pdf")]
+ [InlineData(".rar", "application/vnd.rar")]
+ [InlineData(".ttml", "application/ttml+xml")]
+ [InlineData(".wasm", "application/wasm")]
+ [InlineData(".xml", "application/xml")]
+ [InlineData(".zip", "application/zip")]
+ [InlineData(".bmp", "image/bmp")]
+ [InlineData(".gif", "image/gif")]
+ [InlineData(".ico", "image/vnd.microsoft.icon")]
+ [InlineData(".jpg", "image/jpeg")]
+ [InlineData(".jpeg", "image/jpeg")]
+ [InlineData(".png", "image/png")]
+ [InlineData(".svg", "image/svg+xml")]
+ [InlineData(".svgz", "image/svg+xml")]
+ [InlineData(".tbn", "image/jpeg")]
+ [InlineData(".tif", "image/tiff")]
+ [InlineData(".tiff", "image/tiff")]
+ [InlineData(".webp", "image/webp")]
+ [InlineData(".ttf", "font/ttf")]
+ [InlineData(".woff", "font/woff")]
+ [InlineData(".woff2", "font/woff2")]
+ [InlineData(".ass", "text/x-ssa")]
+ [InlineData(".ssa", "text/x-ssa")]
+ [InlineData(".css", "text/css")]
+ [InlineData(".csv", "text/csv")]
+ [InlineData(".edl", "text/plain")]
+ [InlineData(".txt", "text/plain")]
+ [InlineData(".vtt", "text/vtt")]
+ [InlineData(".3gp", "video/3gpp")]
+ [InlineData(".3g2", "video/3gpp2")]
+ [InlineData(".asf", "video/x-ms-asf")]
+ [InlineData(".avi", "video/x-msvideo")]
+ [InlineData(".flv", "video/x-flv")]
+ [InlineData(".mp4", "video/mp4")]
+ [InlineData(".m4v", "video/x-m4v")]
+ [InlineData(".mpegts", "video/mp2t")]
+ [InlineData(".mpg", "video/mpeg")]
+ [InlineData(".mkv", "video/x-matroska")]
+ [InlineData(".mov", "video/quicktime")]
+ [InlineData(".ogv", "video/ogg")]
+ [InlineData(".ts", "video/mp2t")]
+ [InlineData(".webm", "video/webm")]
+ [InlineData(".wmv", "video/x-ms-wmv")]
+ [InlineData(".aac", "audio/aac")]
+ [InlineData(".ac3", "audio/ac3")]
+ [InlineData(".ape", "audio/x-ape")]
+ [InlineData(".dsf", "audio/dsf")]
+ [InlineData(".dsp", "audio/dsp")]
+ [InlineData(".flac", "audio/flac")]
+ [InlineData(".m4a", "audio/mp4")]
+ [InlineData(".m4b", "audio/m4b")]
+ [InlineData(".mid", "audio/midi")]
+ [InlineData(".midi", "audio/midi")]
+ [InlineData(".mp3", "audio/mpeg")]
+ [InlineData(".oga", "audio/ogg")]
+ [InlineData(".ogg", "audio/ogg")]
+ [InlineData(".opus", "audio/ogg")]
+ [InlineData(".vorbis", "audio/vorbis")]
+ [InlineData(".wav", "audio/wav")]
+ [InlineData(".webma", "audio/webm")]
+ [InlineData(".wma", "audio/x-ms-wma")]
+ [InlineData(".wv", "audio/x-wavpack")]
+ [InlineData(".xsp", "audio/xsp")]
+ public void GetMimeType_Valid_ReturnsCorrectResult(string input, string expectedResult)
+ {
+ Assert.Equal(expectedResult, MimeTypes.GetMimeType(input, null));
+ }
+
+ [Theory]
+ [InlineData("application/epub+zip", ".epub")]
+ [InlineData("application/json", ".json")]
+ [InlineData("application/oebps-package+xml", ".opf")]
+ [InlineData("application/pdf", ".pdf")]
+ [InlineData("application/ttml+xml", ".ttml")]
+ [InlineData("application/vnd.amazon.ebook", ".azw")]
+ [InlineData("application/vnd.ms-fontobject", ".eot")]
+ [InlineData("application/vnd.rar", ".rar")]
+ [InlineData("application/wasm", ".wasm")]
+ [InlineData("application/x-7z-compressed", ".7z")]
+ [InlineData("application/x-cbz", ".cbz")]
+ [InlineData("application/x-javascript", ".js")]
+ [InlineData("application/x-mobipocket-ebook", ".mobi")]
+ [InlineData("application/x-mpegURL", ".m3u8")]
+ [InlineData("application/x-subrip", ".srt")]
+ [InlineData("application/xml", ".xml")]
+ [InlineData("application/zip", ".zip")]
+ [InlineData("audio/aac", ".aac")]
+ [InlineData("audio/ac3", ".ac3")]
+ [InlineData("audio/dsf", ".dsf")]
+ [InlineData("audio/dsp", ".dsp")]
+ [InlineData("audio/flac", ".flac")]
+ [InlineData("audio/m4b", ".m4b")]
+ [InlineData("audio/mp4", ".m4a")]
+ [InlineData("audio/vorbis", ".vorbis")]
+ [InlineData("audio/wav", ".wav")]
+ [InlineData("audio/x-aac", ".aac")]
+ [InlineData("audio/x-ape", ".ape")]
+ [InlineData("audio/x-ms-wma", ".wma")]
+ [InlineData("audio/x-wavpack", ".wv")]
+ [InlineData("audio/xsp", ".xsp")]
+ [InlineData("font/ttf", ".ttf")]
+ [InlineData("font/woff", ".woff")]
+ [InlineData("font/woff2", ".woff2")]
+ [InlineData("image/bmp", ".bmp")]
+ [InlineData("image/gif", ".gif")]
+ [InlineData("image/jpeg", ".jpg")]
+ [InlineData("image/png", ".png")]
+ [InlineData("image/svg+xml", ".svg")]
+ [InlineData("image/tiff", ".tif")]
+ [InlineData("image/vnd.microsoft.icon", ".ico")]
+ [InlineData("image/webp", ".webp")]
+ [InlineData("image/x-png", ".png")]
+ [InlineData("text/css", ".css")]
+ [InlineData("text/csv", ".csv")]
+ [InlineData("text/plain", ".txt")]
+ [InlineData("text/rtf", ".rtf")]
+ [InlineData("text/vtt", ".vtt")]
+ [InlineData("text/x-ssa", ".ssa")]
+ [InlineData("video/3gpp", ".3gp")]
+ [InlineData("video/3gpp2", ".3g2")]
+ [InlineData("video/mp2t", ".ts")]
+ [InlineData("video/mp4", ".mp4")]
+ [InlineData("video/ogg", ".ogv")]
+ [InlineData("video/quicktime", ".mov")]
+ [InlineData("video/vnd.mpeg.dash.mpd", ".mpd")]
+ [InlineData("video/webm", ".webm")]
+ [InlineData("video/x-flv", ".flv")]
+ [InlineData("video/x-m4v", ".m4v")]
+ [InlineData("video/x-matroska", ".mkv")]
+ [InlineData("video/x-ms-asf", ".asf")]
+ [InlineData("video/x-ms-wmv", ".wmv")]
+ [InlineData("video/x-msvideo", ".avi")]
+ public void ToExtension_Valid_ReturnsCorrectResult(string input, string expectedResult)
+ {
+ Assert.Equal(expectedResult, MimeTypes.ToExtension(input));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
index 53b35c2d6..c72a3315e 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
@@ -1,4 +1,3 @@
-using System.Collections.Generic;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using Xunit;
@@ -9,29 +8,29 @@ namespace Jellyfin.Naming.Tests.AudioBook
{
private readonly NamingOptions _namingOptions = new NamingOptions();
- public static IEnumerable<object[]> Resolve_ValidFileNameTestData()
+ public static TheoryData<AudioBookFileInfo> Resolve_ValidFileNameTestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<AudioBookFileInfo>();
+
+ data.Add(
new AudioBookFileInfo(
@"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
- "mp3")
- };
- yield return new object[]
- {
+ "mp3"));
+
+ data.Add(
new AudioBookFileInfo(
@"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
"ogg",
- chapterNumber: 1)
- };
- yield return new object[]
- {
+ chapterNumber: 1));
+
+ data.Add(
new AudioBookFileInfo(
@"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3",
"mp3",
chapterNumber: 2,
- partNumber: 3)
- };
+ partNumber: 3));
+
+ return data;
}
[Theory]
diff --git a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
index 3892d00f6..58aaed023 100644
--- a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
+++ b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
@@ -10,7 +10,6 @@ namespace Jellyfin.Naming.Tests.Common
{
var options = new NamingOptions();
- Assert.NotEmpty(options.VideoFileStackingRegexes);
Assert.NotEmpty(options.CleanDateTimeRegexes);
Assert.NotEmpty(options.CleanStringRegexes);
Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes);
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index e386cb8c1..4096873a3 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -6,19 +6,16 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
index 921c2b1f5..1e7fedb36 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -70,9 +70,10 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("Log Horizon 2/[HorribleSubs] Log Horizon 2 - 03 [720p].mkv", 3)] // digit in series name
[InlineData("Season 1/seriesname 05.mkv", 5)] // no hyphen between series name and episode number
[InlineData("[BBT-RMX] Ranma ½ - 154 [50AC421A].mkv", 154)] // hyphens in the pre-name info, triple digit episode number
- // TODO: [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number
+ [InlineData("Season 2/Episode 21 - 94 Meetings.mp4", 21)] // Title starts with a number
+ [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)]
+ // [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number
// TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)]
- // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)]
// TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)]
// TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)]
// TODO: [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)]
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs
new file mode 100644
index 000000000..ceb5f8b73
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/SeriesPathParserTest.cs
@@ -0,0 +1,28 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class SeriesPathParserTest
+ {
+ [Theory]
+ [InlineData("The.Show.S01", "The.Show")]
+ [InlineData("/The.Show.S01", "The.Show")]
+ [InlineData("/some/place/The.Show.S01", "The.Show")]
+ [InlineData("/something/The.Show.S01", "The.Show")]
+ [InlineData("The Show Season 10", "The Show")]
+ [InlineData("The Show S01E01", "The Show")]
+ [InlineData("The Show S01E01 Episode", "The Show")]
+ [InlineData("/something/The Show/Season 1", "The Show")]
+ [InlineData("/something/The Show/S01", "The Show")]
+ public void SeriesPathParserParseTest(string path, string name)
+ {
+ NamingOptions o = new NamingOptions();
+ var res = SeriesPathParser.Parse(o, path);
+
+ Assert.Equal(name, res.SeriesName);
+ Assert.True(res.Success);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs
new file mode 100644
index 000000000..97f4b4058
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/TV/SeriesResolverTests.cs
@@ -0,0 +1,28 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.TV
+{
+ public class SeriesResolverTests
+ {
+ [Theory]
+ [InlineData("The.Show.S01", "The Show")]
+ [InlineData("The.Show.S01.COMPLETE", "The Show")]
+ [InlineData("S.H.O.W.S01", "S.H.O.W")]
+ [InlineData("The.Show.P.I.S01", "The Show P.I")]
+ [InlineData("The_Show_Season_1", "The Show")]
+ [InlineData("/something/The_Show/Season 10", "The Show")]
+ [InlineData("The Show", "The Show")]
+ [InlineData("/some/path/The Show", "The Show")]
+ [InlineData("/some/path/The Show s02e10 720p hdtv", "The Show")]
+ [InlineData("/some/path/The Show s02e10 the episode 720p hdtv", "The Show")]
+ public void SeriesResolverResolveTest(string path, string name)
+ {
+ NamingOptions o = new NamingOptions();
+ var res = SeriesResolver.Resolve(o, path);
+
+ Assert.Equal(name, res.Name);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
index 89579c037..6d49ac832 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
@@ -21,7 +21,8 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
[InlineData(@"/Foo/The.Series.Name.S01E04.WEBRip.x264-Baz[Bar]/the.series.name.s01e04.webrip.x264-Baz[Bar].mkv", "The.Series.Name", 1, 4)]
[InlineData(@"Love.Death.and.Robots.S01.1080p.NF.WEB-DL.DDP5.1.x264-NTG/Love.Death.and.Robots.S01E01.Sonnies.Edge.1080p.NF.WEB-DL.DDP5.1.x264-NTG.mkv", "Love.Death.and.Robots", 1, 1)]
- // TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
+ [InlineData("[YuiSubs] Tensura Nikki - Tensei Shitara Slime Datta Ken/[YuiSubs] Tensura Nikki - Tensei Shitara Slime Datta Ken - 12 (NVENC H.265 1080p).mkv", "Tensura Nikki - Tensei Shitara Slime Datta Ken", null, 12)]
+ [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
// TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)]
// TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)]
public void TestSimple(string path, string seriesName, int? seasonNumber, int? episodeNumber)
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
index fb050cf5a..1574bce58 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -1,4 +1,3 @@
-using System;
using Emby.Naming.Common;
using Emby.Naming.Video;
using Xunit;
@@ -23,12 +22,17 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("[HorribleSubs] Made in Abyss - 13 [720p].mkv", "Made in Abyss")]
+ [InlineData("[Tsundere] Kore wa Zombie Desu ka of the Dead [BDRip h264 1920x1080 FLAC]", "Kore wa Zombie Desu ka of the Dead")]
+ [InlineData("[Erai-raws] Jujutsu Kaisen - 03 [720p][Multiple Subtitle].mkv", "Jujutsu Kaisen")]
+ [InlineData("[OCN] 애타는 로맨스 720p-NEXT", "애타는 로맨스")]
+ [InlineData("[tvN] 혼술남녀.E01-E16.720p-NEXT", "혼술남녀")]
+ [InlineData("[tvN] 연애말고 결혼 E01~E16 END HDTV.H264.720p-WITH", "연애말고 결혼")]
// FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName)
{
- Assert.True(VideoResolver.TryCleanString(input, _namingOptions, out ReadOnlySpan<char> newName));
- // TODO: compare spans when XUnit supports it
- Assert.Equal(expectedName, newName.ToString());
+ Assert.True(VideoResolver.TryCleanString(input, _namingOptions, out var newName));
+ Assert.Equal(expectedName, newName);
}
[Theory]
@@ -41,8 +45,8 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData("Run lola run (lola rennt) (2009).mp4")]
public void CleanStringTest_DoesntNeedCleaning_False(string? input)
{
- Assert.False(VideoResolver.TryCleanString(input, _namingOptions, out ReadOnlySpan<char> newName));
- Assert.True(newName.IsEmpty);
+ Assert.False(VideoResolver.TryCleanString(input, _namingOptions, out var newName));
+ Assert.True(string.IsNullOrEmpty(newName));
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index f872f94f8..8dd637559 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -18,30 +18,31 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestKodiExtras()
{
- Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
+ Test("trailer.mp4", ExtraType.Trailer);
+ Test("300-trailer.mp4", ExtraType.Trailer);
- Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
+ Test("theme.mp3", ExtraType.ThemeSong);
}
[Fact]
public void TestExpandedExtras()
{
- Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("trailer.mp3", null, _videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
-
- Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
- Test("theme.mkv", null, _videoOptions);
-
- Test("300-scene.mp4", ExtraType.Scene, _videoOptions);
- Test("300-scene2.mp4", ExtraType.Scene, _videoOptions);
- Test("300-clip.mp4", ExtraType.Clip, _videoOptions);
-
- Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions);
- Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions);
- Test("300-interview.mp4", ExtraType.Interview, _videoOptions);
- Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions);
+ Test("trailer.mp4", ExtraType.Trailer);
+ Test("trailer.mp3", null);
+ Test("300-trailer.mp4", ExtraType.Trailer);
+ Test("stuff trailerthings.mkv", null);
+
+ Test("theme.mp3", ExtraType.ThemeSong);
+ Test("theme.mkv", null);
+
+ Test("300-scene.mp4", ExtraType.Scene);
+ Test("300-scene2.mp4", ExtraType.Scene);
+ Test("300-clip.mp4", ExtraType.Clip);
+
+ Test("300-deleted.mp4", ExtraType.DeletedScene);
+ Test("300-deletedscene.mp4", ExtraType.DeletedScene);
+ Test("300-interview.mp4", ExtraType.Interview);
+ Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes);
}
[Theory]
@@ -55,9 +56,9 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData(ExtraType.Unknown, "extras")]
public void TestDirectories(ExtraType type, string dirName)
{
- Test(dirName + "/300.mp4", type, _videoOptions);
- Test("300/" + dirName + "/something.mkv", type, _videoOptions);
- Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions);
+ Test(dirName + "/300.mp4", type);
+ Test("300/" + dirName + "/something.mkv", type);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type);
}
[Theory]
@@ -66,32 +67,23 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData("The Big Short")]
public void TestNonExtraDirectories(string dirName)
{
- Test(dirName + "/300.mp4", null, _videoOptions);
- Test("300/" + dirName + "/something.mkv", null, _videoOptions);
- Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions);
- Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions);
+ Test(dirName + "/300.mp4", null);
+ Test("300/" + dirName + "/something.mkv", null);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null);
+ Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null);
}
[Fact]
public void TestSample()
{
- Test("300-sample.mp4", ExtraType.Sample, _videoOptions);
+ Test("300-sample.mp4", ExtraType.Sample);
}
- private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
+ private void Test(string input, ExtraType? expectedType)
{
- var parser = GetExtraTypeParser(videoOptions);
-
- var extraType = parser.GetExtraInfo(input).ExtraType;
-
- if (expectedType == null)
- {
- Assert.Null(extraType);
- }
- else
- {
- Assert.Equal(expectedType, extraType);
- }
+ var extraType = ExtraResolver.GetExtraInfo(input, _videoOptions).ExtraType;
+
+ Assert.Equal(expectedType, extraType);
}
[Fact]
@@ -99,14 +91,9 @@ namespace Jellyfin.Naming.Tests.Video
{
var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video);
var options = new NamingOptions { VideoExtraRules = new[] { rule } };
- var res = GetExtraTypeParser(options).GetExtraInfo("extra.mp4");
+ var res = ExtraResolver.GetExtraInfo("extra.mp4", options);
Assert.Equal(rule, res.Rule);
}
-
- private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions)
- {
- return new ExtraResolver(videoOptions);
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index d02f8ae92..9a9a57be4 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -23,15 +23,11 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
- Assert.Single(result[0].Extras);
+ Assert.Single(result.Where(v => v.ExtraType == null));
+ Assert.Single(result.Where(v => v.ExtraType != null));
}
[Fact]
@@ -46,15 +42,11 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
- Assert.Single(result[0].Extras);
+ Assert.Single(result.Where(v => v.ExtraType == null));
+ Assert.Single(result.Where(v => v.ExtraType != null));
Assert.Equal(2, result[0].AlternateVersions.Count);
}
@@ -68,11 +60,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -94,15 +82,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(7, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -122,15 +105,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Equal(7, result[0].AlternateVersions.Count);
}
@@ -151,15 +129,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(9, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -176,15 +149,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(5, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -203,15 +171,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(5, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -231,15 +194,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Equal(7, result[0].AlternateVersions.Count);
Assert.False(result[0].AlternateVersions[2].Is3D);
Assert.True(result[0].AlternateVersions[3].Is3D);
@@ -262,15 +220,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Equal(7, result[0].AlternateVersions.Count);
Assert.False(result[0].AlternateVersions[3].Is3D);
Assert.True(result[0].AlternateVersions[4].Is3D);
@@ -287,11 +240,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -312,15 +261,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(7, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -339,15 +283,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(5, result.Count);
- Assert.Empty(result[0].Extras);
Assert.Empty(result[0].AlternateVersions);
}
@@ -361,15 +300,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Single(result[0].AlternateVersions);
}
@@ -383,15 +317,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Single(result[0].AlternateVersions);
}
@@ -405,15 +334,10 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
- Assert.Empty(result[0].Extras);
Assert.Single(result[0].AlternateVersions);
}
@@ -427,11 +351,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -440,7 +360,7 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestEmptyList()
{
- var result = VideoListResolver.Resolve(new List<FileSystemMetadata>(), _namingOptions).ToList();
+ var result = VideoListResolver.Resolve(new List<VideoFileInfo>(), _namingOptions).ToList();
Assert.Empty(result);
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
index 8794d3ebe..368c3592e 100644
--- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
@@ -22,9 +22,7 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
TestStackInfo(result[0], "Bad Boys (2006)", 4);
@@ -39,9 +37,7 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys (2007).mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -55,9 +51,7 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys 2007.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -71,9 +65,7 @@ namespace Jellyfin.Naming.Tests.Video
"300 (2007).mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -87,9 +79,7 @@ namespace Jellyfin.Naming.Tests.Video
"300 2007.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -103,9 +93,7 @@ namespace Jellyfin.Naming.Tests.Video
"Star Trek 2- The wrath of khan.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -119,9 +107,7 @@ namespace Jellyfin.Naming.Tests.Video
"Red Riding in the Year of Our Lord 1974 (2009).mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -135,16 +121,14 @@ namespace Jellyfin.Naming.Tests.Video
"d:/movies/300 2006 part2.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
TestStackInfo(result[0], "300 2006", 2);
}
[Fact]
- public void TestDirtyNames()
+ public void ResolveFiles_GivenPartInMiddleOfName_ReturnsNoStack()
{
var files = new[]
{
@@ -155,16 +139,13 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
- Assert.Single(result);
- TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
+ Assert.Empty(result);
}
[Fact]
- public void TestNumberedFiles()
+ public void ResolveFiles_FileNamesWithMissingPartType_ReturnsNoStack()
{
var files = new[]
{
@@ -175,9 +156,7 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -194,9 +173,7 @@ namespace Jellyfin.Naming.Tests.Video
"300 (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
TestStackInfo(result[0], "300 (2006)", 4);
@@ -214,9 +191,7 @@ namespace Jellyfin.Naming.Tests.Video
"Bad Boys (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
TestStackInfo(result[0], "Bad Boys (2006)", 3);
@@ -238,9 +213,7 @@ namespace Jellyfin.Naming.Tests.Video
"300 (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Equal(2, result.Count);
TestStackInfo(result[1], "Bad Boys (2006)", 4);
@@ -256,9 +229,7 @@ namespace Jellyfin.Naming.Tests.Video
"blah blah - cd 2"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveDirectories(files).ToList();
+ var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
Assert.Single(result);
TestStackInfo(result[0], "blah blah", 2);
@@ -275,9 +246,7 @@ namespace Jellyfin.Naming.Tests.Video
"300-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
@@ -297,9 +266,7 @@ namespace Jellyfin.Naming.Tests.Video
"Avengers part3.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -328,9 +295,7 @@ namespace Jellyfin.Naming.Tests.Video
"300-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Equal(3, result.Count);
@@ -354,9 +319,7 @@ namespace Jellyfin.Naming.Tests.Video
"300 (2006)-trailer.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
@@ -375,9 +338,7 @@ namespace Jellyfin.Naming.Tests.Video
new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true }
};
- var resolver = GetResolver();
-
- var result = resolver.Resolve(files).ToList();
+ var result = StackResolver.Resolve(files, _namingOptions).ToList();
Assert.Equal(2, result.Count);
TestStackInfo(result[0], "300 (2006)", 3);
@@ -397,9 +358,7 @@ namespace Jellyfin.Naming.Tests.Video
"Harry Potter and the Deathly Hallows 4.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Empty(result);
}
@@ -414,9 +373,7 @@ namespace Jellyfin.Naming.Tests.Video
"Neverland (2011)[720p][PG][Voted 6.5][Family-Fantasy]part2.mkv"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveFiles(files).ToList();
+ var result = StackResolver.ResolveFiles(files, _namingOptions).ToList();
Assert.Single(result);
Assert.Equal(2, result[0].Files.Count);
@@ -432,9 +389,7 @@ namespace Jellyfin.Naming.Tests.Video
@"M:/Movies (DVD)/Movies (Musical)/The Sound of Music/The Sound of Music (1965) (Disc 02)"
};
- var resolver = GetResolver();
-
- var result = resolver.ResolveDirectories(files).ToList();
+ var result = StackResolver.ResolveDirectories(files, _namingOptions).ToList();
Assert.Single(result);
Assert.Equal(2, result[0].Files.Count);
@@ -445,10 +400,5 @@ namespace Jellyfin.Naming.Tests.Video
Assert.Equal(fileCount, stack.Files.Count);
Assert.Equal(name, stack.Name);
}
-
- private StackResolver GetResolver()
- {
- return new StackResolver(_namingOptions);
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
index 9e0776c3c..b76187842 100644
--- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Xunit;
@@ -41,23 +42,28 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Equal(5, result.Count);
+ Assert.Equal(11, result.Count);
var batman = result.FirstOrDefault(x => string.Equals(x.Name, "Batman", StringComparison.Ordinal));
Assert.NotNull(batman);
Assert.Equal(3, batman!.Files.Count);
- Assert.Equal(3, batman!.Extras.Count);
var harry = result.FirstOrDefault(x => string.Equals(x.Name, "Harry Potter and the Deathly Hallows", StringComparison.Ordinal));
Assert.NotNull(harry);
Assert.Equal(4, harry!.Files.Count);
- Assert.Equal(2, harry!.Extras.Count);
+
+ Assert.False(result[2].ExtraType.HasValue);
+
+ Assert.Equal(ExtraType.Trailer, result[3].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[4].ExtraType);
+ Assert.Equal(ExtraType.DeletedScene, result[5].ExtraType);
+ Assert.Equal(ExtraType.Sample, result[6].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[7].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[8].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[9].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[10].ExtraType);
}
[Fact]
@@ -70,11 +76,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -90,14 +92,12 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
@@ -110,14 +110,12 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
@@ -131,34 +129,51 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(3, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
}
[Fact]
- public void TestDifferentNames()
+ public void Resolve_SameNameAndYear_ReturnsSingleItem()
{
var files = new[]
{
"Looper (2012)-trailer.mkv",
+ "Looper 2012-trailer.mkv",
"Looper.2012.bluray.720p.x264.mkv"
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(3, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
+ }
+
+ [Fact]
+ public void Resolve_TrailerMatchesFolderName_ReturnsSingleItem()
+ {
+ var files = new[]
+ {
+ "/movies/Looper (2012)/Looper (2012)-trailer.mkv",
+ "/movies/Looper (2012)/Looper.bluray.720p.x264.mkv"
+ };
+
+ var result = VideoListResolver.Resolve(
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
+ _namingOptions).ToList();
+
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
@@ -175,11 +190,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(5, result.Count);
@@ -195,11 +206,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = true,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -216,11 +223,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = true,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, true, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -233,39 +236,18 @@ namespace Jellyfin.Naming.Tests.Video
{
@"No (2012) part1.mp4",
@"No (2012) part2.mp4",
- @"No (2012) part1-trailer.mp4"
- };
-
- var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
- _namingOptions).ToList();
-
- Assert.Single(result);
- }
-
- [Fact]
- public void TestStackedWithTrailer2()
- {
- var files = new[]
- {
- @"No (2012) part1.mp4",
- @"No (2012) part2.mp4",
+ @"No (2012) part1-trailer.mp4",
@"No (2012)-trailer.mp4"
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(3, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
}
[Fact]
@@ -276,18 +258,18 @@ namespace Jellyfin.Naming.Tests.Video
@"/Movies/Top Gun (1984)/movie.mp4",
@"/Movies/Top Gun (1984)/Top Gun (1984)-trailer.mp4",
@"/Movies/Top Gun (1984)/Top Gun (1984)-trailer2.mp4",
- @"trailer.mp4"
+ @"/Movies/trailer.mp4"
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(4, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[2].ExtraType);
+ Assert.Equal(ExtraType.Trailer, result[3].ExtraType);
}
[Fact]
@@ -302,11 +284,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -321,11 +299,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -340,11 +314,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -360,11 +330,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Single(result);
@@ -380,11 +346,7 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
Assert.Equal(2, result.Count);
@@ -396,40 +358,34 @@ namespace Jellyfin.Naming.Tests.Video
var files = new[]
{
@"/Server/Despicable Me/Despicable Me (2010).mkv",
- @"/Server/Despicable Me/movie-trailer.mkv"
+ @"/Server/Despicable Me/trailer.mkv"
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
- public void TestTrailerFalsePositives()
+ public void Resolve_TrailerInTrailersFolder_ReturnsCorrectExtraType()
{
var files = new[]
{
- @"/Server/Despicable Me/Skyscraper (2018) - Big Game Spot.mkv",
- @"/Server/Despicable Me/Skyscraper (2018) - Trailer.mkv",
- @"/Server/Despicable Me/Baywatch (2017) - Big Game Spot.mkv",
- @"/Server/Despicable Me/Baywatch (2017) - Trailer.mkv"
+ @"/Server/Despicable Me/Despicable Me (2010).mkv",
+ @"/Server/Despicable Me/trailers/some title.mkv"
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Equal(4, result.Count);
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
@@ -442,20 +398,18 @@ namespace Jellyfin.Naming.Tests.Video
};
var result = VideoListResolver.Resolve(
- files.Select(i => new FileSystemMetadata
- {
- IsDirectory = false,
- FullName = i
- }).ToList(),
+ files.Select(i => VideoResolver.Resolve(i, false, _namingOptions)).OfType<VideoFileInfo>().ToList(),
_namingOptions).ToList();
- Assert.Single(result);
+ Assert.Equal(2, result.Count);
+ Assert.False(result[0].ExtraType.HasValue);
+ Assert.Equal(ExtraType.Trailer, result[1].ExtraType);
}
[Fact]
public void TestDirectoryStack()
{
- var stack = new FileStack();
+ var stack = new FileStack(string.Empty, false, Array.Empty<string>());
Assert.False(stack.ContainsFile("XX", true));
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
index ac5a7a21e..33a99e107 100644
--- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
@@ -1,4 +1,3 @@
-using System.Collections.Generic;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
@@ -11,148 +10,134 @@ namespace Jellyfin.Naming.Tests.Video
{
private static NamingOptions _namingOptions = new NamingOptions();
- public static IEnumerable<object[]> ResolveFile_ValidFileNameTestData()
+ public static TheoryData<VideoFileInfo> ResolveFile_ValidFileNameTestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<VideoFileInfo>();
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv",
container: "mkv",
- name: "7 Psychos")
- };
- yield return new object[]
- {
+ name: "7 Psychos"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv",
container: "mkv",
name: "3 days to kill",
- year: 2005)
- };
- yield return new object[]
- {
+ year: 2005));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/American Psycho/American.Psycho.mkv",
container: "mkv",
- name: "American.Psycho")
- };
- yield return new object[]
- {
+ name: "American.Psycho"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv",
container: "mkv",
name: "brave",
year: 2006,
is3D: true,
- format3D: "sbs")
- };
- yield return new object[]
- {
+ format3D: "sbs"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv",
container: "mkv",
name: "300",
- year: 2006)
- };
- yield return new object[]
- {
+ year: 2006));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv",
container: "mkv",
name: "300",
year: 2006,
is3D: true,
- format3D: "sbs")
- };
- yield return new object[]
- {
+ format3D: "sbs"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc",
container: "disc",
name: "brave",
year: 2006,
isStub: true,
- stubType: "bluray")
- };
- yield return new object[]
- {
+ stubType: "bluray"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc",
container: "disc",
name: "300",
year: 2006,
isStub: true,
- stubType: "bluray")
- };
- yield return new object[]
- {
+ stubType: "bluray"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc",
container: "disc",
name: "Brave",
year: 2006,
isStub: true,
- stubType: "bluray")
- };
- yield return new object[]
- {
+ stubType: "bluray"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006).bluray.disc",
container: "disc",
name: "300",
year: 2006,
isStub: true,
- stubType: "bluray")
- };
- yield return new object[]
- {
+ stubType: "bluray"));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv",
container: "mkv",
name: "300",
year: 2006,
- extraType: ExtraType.Trailer)
- };
- yield return new object[]
- {
+ extraType: ExtraType.Trailer));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv",
container: "mkv",
name: "Brave",
year: 2006,
- extraType: ExtraType.Trailer)
- };
- yield return new object[]
- {
+ extraType: ExtraType.Trailer));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/300 (2007)/300 (2006).mkv",
container: "mkv",
name: "300",
- year: 2006)
- };
- yield return new object[]
- {
+ year: 2006));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv",
container: "mkv",
name: "Bad Boys",
- year: 1995)
- };
- yield return new object[]
- {
+ year: 1995));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/Brave (2007)/Brave (2006).mkv",
container: "mkv",
name: "Brave",
- year: 2006)
- };
- yield return new object[]
- {
+ year: 2006));
+
+ data.Add(
new VideoFileInfo(
path: @"/server/Movies/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF/Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - JEFF.mp4",
container: "mp4",
name: "Rain Man",
- year: 1988)
- };
+ year: 1988));
+
+ return data;
}
[Theory]
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 97bf673ae..78556ee67 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -6,20 +6,17 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="FsCheck.Xunit" Version="2.15.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="FsCheck.Xunit" Version="2.16.3" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
index 1cad625b7..61f913252 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
@@ -34,7 +34,7 @@ namespace Jellyfin.Networking.Tests
}
/// <summary>
- /// Checks that thge given IP address is not in the network provided.
+ /// Checks that the given IP address is not in the network provided.
/// </summary>
/// <param name="network">Network address(es).</param>
/// <param name="value">The IP to check.</param>
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index 97c14d463..6b9397437 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -20,7 +20,7 @@ namespace Jellyfin.Networking.Tests
CallBase = true
};
configManager.Setup(x => x.GetConfiguration(It.IsAny<string>())).Returns(conf);
- return (IConfigurationManager)configManager.Object;
+ return configManager.Object;
}
/// <summary>
@@ -35,9 +35,9 @@ namespace Jellyfin.Networking.Tests
// eth16 only
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
// All interfaces excluded. (including loopbacks)
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[127.0.0.1/8,::1/128]")]
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
// vEthernet1 and vEthernet212 should be excluded.
- [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24,127.0.0.1/8,::1/128]")]
+ [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
// Overlapping interface,
[InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
@@ -476,5 +476,51 @@ namespace Jellyfin.Networking.Tests
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
}
+
+ [Theory]
+ [InlineData("192.168.1.209/24,-16,eth16", "192.168.1.0/24", "", "192.168.1.209")] // Only 1 address so use it.
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "192.168.1.208")] // LAN address is specified by default.
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "10.0.0.1", "10.0.0.1")] // return bind address
+
+ public void GetBindInterface_NoSourceGiven_Success(string interfaces, string lan, string bind, string result)
+ {
+ var conf = new NetworkConfiguration
+ {
+ EnableIPV4 = true,
+ LocalNetworkSubnets = lan.Split(','),
+ LocalNetworkAddresses = bind.Split(',')
+ };
+
+ NetworkManager.MockNetworkSettings = interfaces;
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ var interfaceToUse = nm.GetBindInterface(string.Empty, out _);
+
+ Assert.Equal(result, interfaceToUse);
+ }
+
+ [Theory]
+ [InlineData("192.168.1.209/24,-16,eth16", "192.168.1.0/24", "", "192.168.1.210", "192.168.1.209")] // Source on LAN
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "192.168.1.209", "192.168.1.208")] // Source on LAN
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "", "8.8.8.8", "10.0.0.1")] // Source external.
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "10.0.0.1", "192.168.1.209", "10.0.0.1")] // LAN not bound, so return external.
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "192.168.1.208,10.0.0.1", "8.8.8.8", "10.0.0.1")] // return external bind address
+ [InlineData("192.168.1.208/24,-16,eth16|10.0.0.1/24,10,eth7", "192.168.1.0/24", "192.168.1.208,10.0.0.1", "192.168.1.210", "192.168.1.208")] // return LAN bind address
+ public void GetBindInterface_ValidSourceGiven_Success(string interfaces, string lan, string bind, string source, string result)
+ {
+ var conf = new NetworkConfiguration
+ {
+ EnableIPV4 = true,
+ LocalNetworkSubnets = lan.Split(','),
+ LocalNetworkAddresses = bind.Split(',')
+ };
+
+ NetworkManager.MockNetworkSettings = interfaces;
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ var interfaceToUse = nm.GetBindInterface(source, out _);
+
+ Assert.Equal(result, interfaceToUse);
+ }
}
}
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 14bd53db5..10767ae23 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -1,23 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <None Include="Test Data\**\*.*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
- <PackageReference Include="coverlet.collector" Version="3.0.3">
+ <PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
new file mode 100644
index 000000000..2ba5c47d7
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
@@ -0,0 +1,597 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Manager;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.Manager
+{
+ public class ItemImageProviderTests
+ {
+ private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg";
+
+ [Fact]
+ public void ValidateImages_PhotoEmptyProviders_NoChange()
+ {
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.ValidateImages(new Photo(), Enumerable.Empty<ILocalImageProvider>(), null);
+
+ Assert.False(changed);
+ }
+
+ [Fact]
+ public void ValidateImages_EmptyItemEmptyProviders_NoChange()
+ {
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.ValidateImages(new Video(), Enumerable.Empty<ILocalImageProvider>(), null);
+
+ Assert.False(changed);
+ }
+
+ private static TheoryData<ImageType, int> GetImageTypesWithCount()
+ {
+ var theoryTypes = new TheoryData<ImageType, int>
+ {
+ // minimal test cases that hit different handling
+ { ImageType.Primary, 1 },
+ { ImageType.Backdrop, 1 },
+ { ImageType.Backdrop, 2 }
+ };
+
+ return theoryTypes;
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void ValidateImages_EmptyItemAndPopulatedProviders_AddsImages(ImageType imageType, int imageCount)
+ {
+ // Has to exist for querying DateModified time on file, results stored but not checked so not populating
+ BaseItem.FileSystem = Mock.Of<IFileSystem>();
+
+ var item = new Video();
+ var imageProvider = GetImageProvider(imageType, imageCount, true);
+
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.ValidateImages(item, new[] { imageProvider }, null);
+
+ Assert.True(changed);
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void ValidateImages_PopulatedItemWithGoodPathsAndEmptyProviders_NoChange(ImageType imageType, int imageCount)
+ {
+ var item = GetItemWithImages(imageType, imageCount, true);
+
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.ValidateImages(item, Enumerable.Empty<ILocalImageProvider>(), null);
+
+ Assert.False(changed);
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void ValidateImages_PopulatedItemWithBadPathsAndEmptyProviders_RemovesImage(ImageType imageType, int imageCount)
+ {
+ var item = GetItemWithImages(imageType, imageCount, false);
+
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.ValidateImages(item, Enumerable.Empty<ILocalImageProvider>(), null);
+
+ Assert.True(changed);
+ Assert.Empty(item.GetImages(imageType));
+ }
+
+ [Fact]
+ public void MergeImages_EmptyItemNewImagesEmpty_NoChange()
+ {
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.MergeImages(new Video(), Array.Empty<LocalImageInfo>());
+
+ Assert.False(changed);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void MergeImages_PopulatedItemWithGoodPathsAndPopulatedNewImages_AddsUpdatesImages(ImageType imageType, int imageCount)
+ {
+ // valid and not valid paths - should replace the valid paths with the invalid ones
+ var item = GetItemWithImages(imageType, imageCount, true);
+ var images = GetImages(imageType, imageCount, false);
+
+ var itemImageProvider = GetItemImageProvider(null, null);
+ var changed = itemImageProvider.MergeImages(item, images);
+
+ Assert.True(changed);
+ // adds for types that allow multiple, replaces singular type images
+ if (item.AllowsMultipleImages(imageType))
+ {
+ Assert.Equal(imageCount * 2, item.GetImages(imageType).Count());
+ }
+ else
+ {
+ Assert.Single(item.GetImages(imageType));
+ Assert.Same(images[0].FileInfo.FullName, item.GetImages(imageType).First().Path);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImages_NoChange(ImageType imageType, int imageCount)
+ {
+ var oldTime = new DateTime(1970, 1, 1);
+
+ // match update time with time added to item images (unix epoch)
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny<FileSystemMetadata>()))
+ .Returns(oldTime);
+ BaseItem.FileSystem = fileSystem.Object;
+
+ // all valid paths - matching for strictly updating
+ var item = GetItemWithImages(imageType, imageCount, true);
+ // set size to non-zero to allow for updates to occur
+ foreach (var image in item.GetImages(imageType))
+ {
+ image.DateModified = oldTime;
+ image.Height = 1;
+ image.Width = 1;
+ }
+
+ var images = GetImages(imageType, imageCount, true);
+
+ var itemImageProvider = GetItemImageProvider(null, fileSystem);
+ var changed = itemImageProvider.MergeImages(item, images);
+
+ Assert.False(changed);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImagesWithNewTimestamps_ResetsImageSizes(ImageType imageType, int imageCount)
+ {
+ var oldTime = new DateTime(1970, 1, 1);
+ var updatedTime = new DateTime(2021, 1, 1);
+
+ var fileSystem = new Mock<IFileSystem>();
+ fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny<FileSystemMetadata>()))
+ .Returns(updatedTime);
+ BaseItem.FileSystem = fileSystem.Object;
+
+ // all valid paths - matching for strictly updating
+ var item = GetItemWithImages(imageType, imageCount, true);
+ // set size to non-zero to allow for image size reset to occur
+ foreach (var image in item.GetImages(imageType))
+ {
+ image.DateModified = oldTime;
+ image.Height = 1;
+ image.Width = 1;
+ }
+
+ var images = GetImages(imageType, imageCount, true);
+
+ var itemImageProvider = GetItemImageProvider(null, fileSystem);
+ var changed = itemImageProvider.MergeImages(item, images);
+
+ Assert.True(changed);
+ // before and after paths are the same, verify updated by size reset to 0
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ foreach (var image in item.GetImages(imageType))
+ {
+ Assert.Equal(updatedTime, image.DateModified);
+ Assert.Equal(0, image.Height);
+ Assert.Equal(0, image.Width);
+ }
+ }
+
+ [Theory]
+ [InlineData(ImageType.Primary, 1, false)]
+ [InlineData(ImageType.Backdrop, 2, false)]
+ [InlineData(ImageType.Primary, 1, true)]
+ [InlineData(ImageType.Backdrop, 2, true)]
+ public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh)
+ {
+ var item = GetItemWithImages(imageType, imageCount, false);
+
+ var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
+
+ var imageResponse = new DynamicImageResponse
+ {
+ HasImage = true,
+ Format = ImageFormat.Jpg,
+ Path = "url path",
+ Protocol = MediaProtocol.Http
+ };
+
+ var dynamicProvider = new Mock<IDynamicImageProvider>(MockBehavior.Strict);
+ dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider");
+ dynamicProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+ dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny<CancellationToken>()))
+ .ReturnsAsync(imageResponse);
+
+ var refreshOptions = forceRefresh
+ ? new ImageRefreshOptions(Mock.Of<IDirectoryService>())
+ {
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ ReplaceAllImages = true
+ }
+ : new ImageRefreshOptions(Mock.Of<IDirectoryService>());
+
+ var itemImageProvider = GetItemImageProvider(null, new Mock<IFileSystem>());
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { dynamicProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ if (forceRefresh)
+ {
+ // replaces multi-types
+ Assert.Single(item.GetImages(imageType));
+ }
+ else
+ {
+ // adds to multi-types if room
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ }
+ }
+
+ [Theory]
+ [InlineData(ImageType.Primary, 1, true, MediaProtocol.Http)]
+ [InlineData(ImageType.Backdrop, 2, true, MediaProtocol.Http)]
+ [InlineData(ImageType.Primary, 1, true, MediaProtocol.File)]
+ [InlineData(ImageType.Backdrop, 2, true, MediaProtocol.File)]
+ [InlineData(ImageType.Primary, 1, false, MediaProtocol.File)]
+ [InlineData(ImageType.Backdrop, 2, false, MediaProtocol.File)]
+ public async void RefreshImages_EmptyItemPopulatedProviderDynamic_AddsImages(ImageType imageType, int imageCount, bool responseHasPath, MediaProtocol protocol)
+ {
+ // Has to exist for querying DateModified time on file, results stored but not checked so not populating
+ BaseItem.FileSystem = Mock.Of<IFileSystem>();
+
+ var item = new Video();
+
+ var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
+
+ // Path must exist if set: is read in as a stream by AsyncFile.OpenRead
+ var imageResponse = new DynamicImageResponse
+ {
+ HasImage = true,
+ Format = ImageFormat.Jpg,
+ Path = responseHasPath ? string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0) : null,
+ Protocol = protocol
+ };
+
+ var dynamicProvider = new Mock<IDynamicImageProvider>(MockBehavior.Strict);
+ dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider");
+ dynamicProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+ dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny<CancellationToken>()))
+ .ReturnsAsync(imageResponse);
+
+ var refreshOptions = new ImageRefreshOptions(Mock.Of<IDirectoryService>());
+
+ var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
+ providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<Stream>(), It.IsAny<string>(), imageType, null, It.IsAny<CancellationToken>()))
+ .Callback<BaseItem, Stream, string, ImageType, int?, CancellationToken>((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata()))
+ .Returns(Task.CompletedTask);
+ var itemImageProvider = GetItemImageProvider(providerManager.Object, null);
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { dynamicProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ // dynamic provider unable to return multiple images
+ Assert.Single(item.GetImages(imageType));
+ if (protocol == MediaProtocol.Http)
+ {
+ Assert.Equal(imageResponse.Path, item.GetImagePath(imageType, 0));
+ }
+ }
+
+ [Theory]
+ [InlineData(ImageType.Primary, 1, false)]
+ [InlineData(ImageType.Backdrop, 1, false)]
+ [InlineData(ImageType.Backdrop, 2, false)]
+ [InlineData(ImageType.Primary, 1, true)]
+ [InlineData(ImageType.Backdrop, 1, true)]
+ [InlineData(ImageType.Backdrop, 2, true)]
+ public async void RefreshImages_PopulatedItemPopulatedProviderRemote_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh)
+ {
+ var item = GetItemWithImages(imageType, imageCount, false);
+
+ var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
+
+ var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
+ remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
+ remoteProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+
+ var refreshOptions = forceRefresh
+ ? new ImageRefreshOptions(Mock.Of<IDirectoryService>())
+ {
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ ReplaceAllImages = true
+ }
+ : new ImageRefreshOptions(Mock.Of<IDirectoryService>());
+
+ var remoteInfo = new RemoteImageInfo[imageCount];
+ for (int i = 0; i < imageCount; i++)
+ {
+ remoteInfo[i] = new RemoteImageInfo
+ {
+ Type = imageType,
+ Url = "image url " + i,
+ Width = 1 // min width is set to 0, this will always pass
+ };
+ }
+
+ var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
+ providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync(remoteInfo);
+ var itemImageProvider = GetItemImageProvider(providerManager.Object, new Mock<IFileSystem>());
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ foreach (var image in item.GetImages(imageType))
+ {
+ if (forceRefresh)
+ {
+ Assert.Matches(@"image url [0-9]", image.Path);
+ }
+ else
+ {
+ Assert.DoesNotMatch(@"image url [0-9]", image.Path);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(ImageType.Primary, 0, false)] // singular type only fetches if type is missing from item, no caching
+ [InlineData(ImageType.Backdrop, 0, false)] // empty item, no cache to check
+ [InlineData(ImageType.Backdrop, 1, false)] // populated item, cached so no download
+ [InlineData(ImageType.Backdrop, 1, true)] // populated item, forced to download
+ public async void RefreshImages_NonStubItemPopulatedProviderRemote_DownloadsIfNecessary(ImageType imageType, int initialImageCount, bool fullRefresh)
+ {
+ var targetImageCount = 1;
+
+ // Set path and media source manager so images will be downloaded (EnableImageStub will return false)
+ var item = GetItemWithImages(imageType, initialImageCount, false);
+ item.Path = "non-empty path";
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ // seek 2 so it won't short-circuit out of downloading when populated
+ var libraryOptions = GetLibraryOptions(item, imageType, 2);
+
+ const string Content = "Content";
+ var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
+ remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
+ remoteProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+ remoteProvider.Setup(rp => rp.GetImageResponse(It.IsAny<string>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync((string url, CancellationToken _) => new HttpResponseMessage
+ {
+ ReasonPhrase = url,
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(Content, Encoding.UTF8, "image/jpeg")
+ });
+
+ var refreshOptions = fullRefresh
+ ? new ImageRefreshOptions(Mock.Of<IDirectoryService>())
+ {
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ ReplaceAllImages = true
+ }
+ : new ImageRefreshOptions(Mock.Of<IDirectoryService>());
+
+ var remoteInfo = new RemoteImageInfo[targetImageCount];
+ for (int i = 0; i < targetImageCount; i++)
+ {
+ remoteInfo[i] = new RemoteImageInfo()
+ {
+ Type = imageType,
+ Url = "image url " + i,
+ Width = 1 // min width is set to 0, this will always pass
+ };
+ }
+
+ var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
+ providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync(remoteInfo);
+ providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<Stream>(), It.IsAny<string>(), imageType, null, It.IsAny<CancellationToken>()))
+ .Callback<BaseItem, Stream, string, ImageType, int?, CancellationToken>((callbackItem, _, _, callbackType, _, _) =>
+ callbackItem.SetImagePath(callbackType, callbackItem.AllowsMultipleImages(callbackType) ? callbackItem.GetImages(callbackType).Count() : 0, new FileSystemMetadata()))
+ .Returns(Task.CompletedTask);
+ var fileSystem = new Mock<IFileSystem>();
+ // match reported file size to image content length - condition for skipping already downloaded multi-images
+ fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny<string>()))
+ .Returns(new FileSystemMetadata { Length = Content.Length });
+ var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem);
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.Equal(initialImageCount == 0 || fullRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ Assert.Equal(targetImageCount, item.GetImages(imageType).Count());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public async void RefreshImages_EmptyItemPopulatedProviderRemoteExtras_LimitsImages(ImageType imageType, int imageCount)
+ {
+ var item = new Video();
+
+ var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
+
+ var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
+ remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
+ remoteProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+
+ var refreshOptions = new ImageRefreshOptions(Mock.Of<IDirectoryService>());
+
+ // populate remote with double the required images to verify count is trimmed to the library option count
+ var remoteInfoCount = imageCount * 2;
+ var remoteInfo = new RemoteImageInfo[remoteInfoCount];
+ for (int i = 0; i < remoteInfoCount; i++)
+ {
+ remoteInfo[i] = new RemoteImageInfo()
+ {
+ Type = imageType,
+ Url = "image url " + i,
+ Width = 1 // min width is set to 0, this will always pass
+ };
+ }
+
+ var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
+ providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
+ .ReturnsAsync(remoteInfo);
+ var itemImageProvider = GetItemImageProvider(providerManager.Object, null);
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ var actualImages = item.GetImages(imageType).ToList();
+ Assert.Equal(imageCount, actualImages.Count);
+ // images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen
+ foreach (var image in actualImages)
+ {
+ var index = int.Parse(Regex.Match(image.Path, @"[0-9]+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture);
+ Assert.True(index < imageCount);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImageTypesWithCount))]
+ public async void RefreshImages_PopulatedItemEmptyProviderRemoteFullRefresh_DoesntClearImages(ImageType imageType, int imageCount)
+ {
+ var item = GetItemWithImages(imageType, imageCount, false);
+
+ var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
+
+ var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
+ remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
+ remoteProvider.Setup(rp => rp.GetSupportedImages(item))
+ .Returns(new[] { imageType });
+
+ var refreshOptions = new ImageRefreshOptions(Mock.Of<IDirectoryService>())
+ {
+ ImageRefreshMode = MetadataRefreshMode.FullRefresh,
+ ReplaceAllImages = true
+ };
+
+ var itemImageProvider = GetItemImageProvider(Mock.Of<IProviderManager>(), null);
+ var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
+
+ Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
+ Assert.Equal(imageCount, item.GetImages(imageType).Count());
+ }
+
+ private static ItemImageProvider GetItemImageProvider(IProviderManager? providerManager, Mock<IFileSystem>? mockFileSystem)
+ {
+ // strict to ensure this isn't accidentally used where a prepared mock is intended
+ providerManager ??= Mock.Of<IProviderManager>(MockBehavior.Strict);
+
+ // BaseItem.ValidateImages depends on the directory service being able to list directory contents, give it the expected valid file paths
+ mockFileSystem ??= new Mock<IFileSystem>(MockBehavior.Strict);
+ mockFileSystem.Setup(fs => fs.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>()))
+ .Returns(new[]
+ {
+ string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0),
+ string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 1)
+ });
+
+ return new ItemImageProvider(new NullLogger<ItemImageProvider>(), providerManager, mockFileSystem.Object);
+ }
+
+ private static BaseItem GetItemWithImages(ImageType type, int count, bool validPaths)
+ {
+ // Has to exist for querying DateModified time on file, results stored but not checked so not populating
+ BaseItem.FileSystem ??= Mock.Of<IFileSystem>();
+
+ var item = new Video();
+
+ var path = validPaths ? TestDataImagePath : "invalid path {0}";
+ for (int i = 0; i < count; i++)
+ {
+ item.SetImagePath(type, i, new FileSystemMetadata
+ {
+ FullName = string.Format(CultureInfo.InvariantCulture, path, i),
+ });
+ }
+
+ return item;
+ }
+
+ private static ILocalImageProvider GetImageProvider(ImageType type, int count, bool validPaths)
+ {
+ var images = GetImages(type, count, validPaths);
+
+ var imageProvider = new Mock<ILocalImageProvider>();
+ imageProvider.Setup(ip => ip.GetImages(It.IsAny<BaseItem>(), It.IsAny<IDirectoryService>()))
+ .Returns(images);
+ return imageProvider.Object;
+ }
+
+ /// <summary>
+ /// Creates a list of <see cref="LocalImageInfo"/> references of the specified type and size, optionally pointing to files that exist.
+ /// </summary>
+ private static LocalImageInfo[] GetImages(ImageType type, int count, bool validPaths)
+ {
+ var path = validPaths ? TestDataImagePath : "invalid path {0}";
+ var images = new LocalImageInfo[count];
+ for (int i = 0; i < count; i++)
+ {
+ images[i] = new LocalImageInfo
+ {
+ Type = type,
+ FileInfo = new FileSystemMetadata
+ {
+ FullName = string.Format(CultureInfo.InvariantCulture, path, i)
+ }
+ };
+ }
+
+ return images;
+ }
+
+ /// <summary>
+ /// Generates a <see cref="LibraryOptions"/> object that will allow for the requested number of images for the target type.
+ /// </summary>
+ private static LibraryOptions GetLibraryOptions(BaseItem item, ImageType type, int count)
+ {
+ return new LibraryOptions
+ {
+ TypeOptions = new[]
+ {
+ new TypeOptions
+ {
+ Type = item.GetType().Name,
+ ImageOptions = new[]
+ {
+ new ImageOption
+ {
+ Type = type,
+ Limit = count,
+ MinWidth = 0
+ }
+ }
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
new file mode 100644
index 000000000..558321810
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class EmbeddedImageProviderTests
+ {
+ [Theory]
+ [InlineData(typeof(AudioBook))]
+ [InlineData(typeof(BoxSet))]
+ [InlineData(typeof(Series))]
+ [InlineData(typeof(Season))]
+ [InlineData(typeof(Episode), ImageType.Primary)]
+ [InlineData(typeof(Movie), ImageType.Logo, ImageType.Backdrop, ImageType.Primary)]
+ public void GetSupportedImages_AnyBaseItem_ReturnsExpected(Type type, params ImageType[] expected)
+ {
+ BaseItem item = (BaseItem)Activator.CreateInstance(type)!;
+ var embeddedImageProvider = new EmbeddedImageProvider(Mock.Of<IMediaSourceManager>(), Mock.Of<IMediaEncoder>(), new NullLogger<EmbeddedImageProvider>());
+ var actual = embeddedImageProvider.GetSupportedImages(item);
+ Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString()));
+ }
+
+ [Fact]
+ public async void GetImage_NoStreams_ReturnsNoImage()
+ {
+ var input = new Movie();
+
+ var mediaSourceManager = GetMediaSourceManager(input, new List<MediaAttachment>(), new List<MediaStream>());
+ var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, null, new NullLogger<EmbeddedImageProvider>());
+
+ var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Theory]
+ [InlineData("chapter", null, 1, ImageType.Chapter, null)] // unexpected type, nothing found
+ [InlineData("unmatched", null, 1, ImageType.Primary, null)] // doesn't default on no match
+ [InlineData("clearlogo.png", null, 1, ImageType.Logo, ImageFormat.Png)] // extract extension from name
+ [InlineData("backdrop", "image/bmp", 2, ImageType.Backdrop, ImageFormat.Bmp)] // extract extension from mimetype
+ [InlineData("poster", null, 3, ImageType.Primary, ImageFormat.Jpg)] // default extension to jpg
+ public async void GetImage_Attachment_ReturnsCorrectSelection(string filename, string mimetype, int targetIndex, ImageType type, ImageFormat? expectedFormat)
+ {
+ var attachments = new List<MediaAttachment>();
+ string pathPrefix = "path";
+ for (int i = 1; i <= targetIndex; i++)
+ {
+ var name = i == targetIndex ? filename : "unmatched";
+ attachments.Add(new ()
+ {
+ FileName = name,
+ MimeType = mimetype,
+ Index = i
+ });
+ }
+
+ var input = new Movie();
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<int>(), It.IsAny<ImageFormat>(), It.IsAny<CancellationToken>()))
+ .Returns<string, string, MediaSourceInfo, MediaStream, int, ImageFormat, CancellationToken>((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + "." + ext));
+ var mediaSourceManager = GetMediaSourceManager(input, attachments, new List<MediaStream>());
+ var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());
+
+ var actual = await embeddedImageProvider.GetImage(input, type, CancellationToken.None);
+ Assert.NotNull(actual);
+ if (expectedFormat == null)
+ {
+ Assert.False(actual.HasImage);
+ }
+ else
+ {
+ Assert.True(actual.HasImage);
+ Assert.Equal(pathPrefix + targetIndex + "." + expectedFormat, actual.Path, StringComparer.OrdinalIgnoreCase);
+ Assert.Equal(expectedFormat, actual.Format);
+ }
+ }
+
+ [Theory]
+ [InlineData("chapter", null, 1, ImageType.Chapter, null)] // unexpected type, nothing found
+ [InlineData(null, null, 1, ImageType.Backdrop, null)] // no label, can only find primary
+ [InlineData(null, null, 1, ImageType.Primary, ImageFormat.Jpg)] // no label, finds primary
+ [InlineData("backdrop", null, 2, ImageType.Backdrop, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream
+ [InlineData("cover", null, 2, ImageType.Primary, ImageFormat.Jpg)] // uses label to find index 2, not just pulling first stream
+ [InlineData(null, "mjpeg", 1, ImageType.Primary, ImageFormat.Jpg)]
+ [InlineData(null, "png", 1, ImageType.Primary, ImageFormat.Png)]
+ [InlineData(null, "gif", 1, ImageType.Primary, ImageFormat.Gif)]
+ public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string? codec, int targetIndex, ImageType type, ImageFormat? expectedFormat)
+ {
+ var streams = new List<MediaStream>();
+ for (int i = 1; i <= targetIndex; i++)
+ {
+ var comment = i == targetIndex ? label : "unmatched";
+ streams.Add(new ()
+ {
+ Type = MediaStreamType.EmbeddedImage,
+ Index = i,
+ Comment = comment,
+ Codec = codec
+ });
+ }
+
+ var input = new Movie();
+
+ var pathPrefix = "path";
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<int>(), It.IsAny<ImageFormat>(), It.IsAny<CancellationToken>()))
+ .Returns<string, string, MediaSourceInfo, MediaStream, int, ImageFormat, CancellationToken>((_, _, _, stream, index, ext, _) =>
+ {
+ Assert.Equal(streams[index - 1], stream);
+ return Task.FromResult(pathPrefix + index + "." + ext);
+ });
+ var mediaSourceManager = GetMediaSourceManager(input, new List<MediaAttachment>(), streams);
+ var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());
+
+ var actual = await embeddedImageProvider.GetImage(input, type, CancellationToken.None);
+ Assert.NotNull(actual);
+ if (expectedFormat == null)
+ {
+ Assert.False(actual.HasImage);
+ }
+ else
+ {
+ Assert.True(actual.HasImage);
+ Assert.Equal(pathPrefix + targetIndex + "." + expectedFormat, actual.Path, StringComparer.OrdinalIgnoreCase);
+ Assert.Equal(expectedFormat, actual.Format);
+ }
+ }
+
+ private static IMediaSourceManager GetMediaSourceManager(BaseItem item, List<MediaAttachment> mediaAttachments, List<MediaStream> mediaStreams)
+ {
+ var mediaSourceManager = new Mock<IMediaSourceManager>(MockBehavior.Strict);
+ mediaSourceManager.Setup(i => i.GetMediaAttachments(item.Id))
+ .Returns(mediaAttachments);
+ mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Type == MediaStreamType.EmbeddedImage)))
+ .Returns(mediaStreams);
+ return mediaSourceManager.Object;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
index b160e676e..33da277e3 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CA1002 // Do not expose generic lists
+#pragma warning disable CA1002 // Do not expose generic lists
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
@@ -11,11 +11,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo
{
public class SubtitleResolverTests
{
- public static IEnumerable<object[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData()
+ public static TheoryData<List<MediaStream>, string, int, string[], MediaStream[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData()
{
+ var data = new TheoryData<List<MediaStream>, string, int, string[], MediaStream[]>();
+
var index = 0;
- yield return new object[]
- {
+ data.Add(
new List<MediaStream>(),
"/video/My.Video.mkv",
index,
@@ -52,8 +53,9 @@ namespace Jellyfin.Providers.Tests.MediaInfo
CreateMediaStream("/video/My.Video.default.forced.en.srt", "srt", "en", index++, isForced: true, isDefault: true),
CreateMediaStream("/video/My.Video.en.default.forced.srt", "srt", "en", index++, isForced: true, isDefault: true),
CreateMediaStream("/video/My.Video.With.Additional.Garbage.en.srt", "srt", "en", index),
- }
- };
+ });
+
+ return data;
}
[Theory]
@@ -78,6 +80,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo
}
}
+ [Theory]
+ [InlineData("/video/My Video.mkv", "/video/My Video.srt", "srt", null, false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.srt", "srt", null, false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.foreign.srt", "srt", null, true, false)]
+ [InlineData("/video/My Video.mkv", "/video/My Video.forced.srt", "srt", null, true, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.srt", "srt", null, false, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.forced.default.srt", "srt", null, true, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.en.srt", "srt", "en", false, false)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.en.srt", "srt", "en", false, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.default.forced.en.srt", "srt", "en", true, true)]
+ [InlineData("/video/My.Video.mkv", "/video/My.Video.en.default.forced.srt", "srt", "en", true, true)]
+ public void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string videoPath, string file, string codec, string? language, bool isForced, bool isDefault)
+ {
+ var streams = new List<MediaStream>();
+ var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault);
+
+ new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file });
+
+ Assert.Single(streams);
+
+ var actual = streams[0];
+
+ Assert.Equal(expected.Index, actual.Index);
+ Assert.Equal(expected.Type, actual.Type);
+ Assert.Equal(expected.IsExternal, actual.IsExternal);
+ Assert.Equal(expected.Path, actual.Path);
+ Assert.Equal(expected.IsDefault, actual.IsDefault);
+ Assert.Equal(expected.IsForced, actual.IsForced);
+ Assert.Equal(expected.Language, actual.Language);
+ }
+
private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false)
{
return new ()
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
new file mode 100644
index 000000000..839925dd1
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class VideoImageProviderTests
+ {
+ private static TheoryData<Video> GetImage_UnsupportedInput_ReturnsNoImage_TestData()
+ {
+ return new ()
+ {
+ new Movie { IsPlaceHolder = true },
+
+ new Movie { DefaultVideoStreamIndex = null },
+
+ // set a default index but don't put anything there (invalid input, but provider shouldn't break)
+ new Movie { DefaultVideoStreamIndex = 0 }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(GetImage_UnsupportedInput_ReturnsNoImage_TestData))]
+ public async void GetImage_UnsupportedInput_ReturnsNoImage(Video input)
+ {
+ var mediaSourceManager = GetMediaSourceManager(input, null, new List<MediaStream>());
+ var videoImageProvider = new VideoImageProvider(mediaSourceManager, Mock.Of<IMediaEncoder>(), new NullLogger<VideoImageProvider>());
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Theory]
+ [InlineData(1, 1)] // default not first stream
+ [InlineData(5, 0)] // default out of valid range
+ public async void GetImage_DefaultVideoStreams_ReturnsCorrectStreamImage(int defaultIndex, int targetIndex)
+ {
+ var input = new Movie { DefaultVideoStreamIndex = defaultIndex };
+
+ string targetPath = "path.jpg";
+ var mediaStreams = new List<MediaStream>();
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+
+ for (int i = 0; i <= targetIndex; i++)
+ {
+ var mediaStream = new MediaStream { Type = MediaStreamType.Video, Index = i };
+ mediaStreams.Add(mediaStream);
+
+ var path = i == targetIndex ? targetPath : "wrong stream called!";
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), mediaStream, It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), It.IsAny<CancellationToken>()))
+ .Returns(Task.FromResult(path));
+ }
+
+ var defaultStream = defaultIndex < mediaStreams.Count ? mediaStreams[targetIndex] : null;
+ var mediaSourceManager = GetMediaSourceManager(input, defaultStream, mediaStreams);
+
+ var videoImageProvider = new VideoImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<VideoImageProvider>());
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Theory]
+ [InlineData(null, 10)] // default time
+ [InlineData(500, 50)] // calculated time
+ public async void GetImage_TimeSpan_SelectsCorrectTime(int? runTimeSeconds, long expectedSeconds)
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ var input = new Movie
+ {
+ DefaultVideoStreamIndex = 0,
+ RunTimeTicks = runTimeSeconds * TimeSpan.TicksPerSecond
+ };
+
+ var mediaSourceManager = GetMediaSourceManager(input, targetStream, new List<MediaStream> { targetStream });
+
+ // use a callback to catch the actual value
+ // provides more information on failure than verifying a specific input was called on the mock
+ TimeSpan? actualTimeSpan = null;
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Callback<string, string, MediaSourceInfo, MediaStream, Video3DFormat?, TimeSpan?, CancellationToken>((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan)
+ .Returns(Task.FromResult("path"));
+
+ var videoImageProvider = new VideoImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<VideoImageProvider>());
+
+ // not testing return, just verifying what gets requested for time span
+ await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+
+ Assert.Equal(TimeSpan.FromSeconds(expectedSeconds), actualTimeSpan);
+ }
+
+ private static IMediaSourceManager GetMediaSourceManager(Video item, MediaStream? defaultStream, List<MediaStream> mediaStreams)
+ {
+ var defaultStreamList = new List<MediaStream>();
+ if (defaultStream != null)
+ {
+ defaultStreamList.Add(defaultStream);
+ }
+
+ var mediaSourceManager = new Mock<IMediaSourceManager>(MockBehavior.Strict);
+ mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Index == item.DefaultVideoStreamIndex)))
+ .Returns(defaultStreamList);
+ mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Type == MediaStreamType.Video)))
+ .Returns(mediaStreams);
+ return mediaSourceManager.Object;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs
index efe8063a0..25900bc09 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs
@@ -1,10 +1,9 @@
using System.Text.Json;
using System.Text.Json.Serialization;
-using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Providers.Plugins.Omdb;
using Xunit;
-namespace Jellyfin.Common.Tests.Json
+namespace Jellyfin.Providers.Tests.Omdb
{
public class JsonOmdbConverterTests
{
diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank0.jpg
diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/Test Data/Images/blank1.jpg
diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs
index f6a7c676f..efd2d9553 100644
--- a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs
@@ -23,5 +23,18 @@ namespace Jellyfin.Providers.Tests.Tmdb
{
Assert.Equal(expected, TmdbUtils.NormalizeLanguage(input!));
}
+
+ [Theory]
+ [InlineData(null, null, null)]
+ [InlineData(null, "en-US", null)]
+ [InlineData("en", null, "en")]
+ [InlineData("en", "en-US", "en-US")]
+ [InlineData("fr-CA", "fr-BE", "fr-CA")]
+ [InlineData("fr-CA", "fr", "fr-CA")]
+ [InlineData("de", "en-US", "de")]
+ public static void AdjustImageLanguage_Valid_Success(string imageLanguage, string requestLanguage, string expected)
+ {
+ Assert.Equal(expected, TmdbUtils.AdjustImageLanguage(imageLanguage, requestLanguage));
+ }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
index f312933fb..6337dea41 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
@@ -32,10 +32,11 @@ namespace Jellyfin.Server.Implementations.Tests.Data
_sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
}
- public static IEnumerable<object[]> ItemImageInfoFromValueString_Valid_TestData()
+ public static TheoryData<string, ItemImageInfo> ItemImageInfoFromValueString_Valid_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, ItemImageInfo>();
+
+ data.Add(
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
new ItemImageInfo
{
@@ -45,41 +46,33 @@ namespace Jellyfin.Server.Implementations.Tests.Data
Width = 1920,
Height = 1080,
BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
new ItemImageInfo
{
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
Type = ImageType.Primary,
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary",
new ItemImageInfo
{
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
Type = ImageType.Primary,
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*600",
new ItemImageInfo
{
Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
Type = ImageType.Primary,
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
new ItemImageInfo
{
@@ -88,8 +81,9 @@ namespace Jellyfin.Server.Implementations.Tests.Data
DateModified = new DateTime(637264380567586027, DateTimeKind.Utc),
Width = 600,
Height = 336
- }
- };
+ });
+
+ return data;
}
[Theory]
@@ -109,15 +103,18 @@ namespace Jellyfin.Server.Implementations.Tests.Data
[InlineData("")]
[InlineData("*")]
[InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
+ [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*6374520964785129080*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid modified date
+ [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*-637452096478512963*WjQbtJtSO8nhNZ%L_Io#R/oaS<o}-;adXAoIn7j[%hW9s:WGw[nN")] // Negative modified date
+ [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Invalid*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN")] // Invalid type
public void ItemImageInfoFromValueString_Invalid_Null(string value)
{
Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
}
- public static IEnumerable<object[]> DeserializeImages_Valid_TestData()
+ public static TheoryData<string, ItemImageInfo[]> DeserializeImages_Valid_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, ItemImageInfo[]>();
+ data.Add(
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
new ItemImageInfo[]
{
@@ -130,11 +127,9 @@ namespace Jellyfin.Server.Implementations.Tests.Data
Height = 1080,
BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
}
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0",
new ItemImageInfo[]
{
@@ -162,20 +157,19 @@ namespace Jellyfin.Server.Implementations.Tests.Data
Type = ImageType.Backdrop,
DateModified = new DateTime(637261226721685297, DateTimeKind.Utc),
}
- }
- };
+ });
+
+ return data;
}
- public static IEnumerable<object[]> DeserializeImages_ValidAndInvalid_TestData()
+ public static TheoryData<string, ItemImageInfo[]> DeserializeImages_ValidAndInvalid_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, ItemImageInfo[]>();
+ data.Add(
string.Empty,
- Array.Empty<ItemImageInfo>()
- };
+ Array.Empty<ItemImageInfo>());
- yield return new object[]
- {
+ data.Add(
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN|test|1234||ss",
new ItemImageInfo[]
{
@@ -188,14 +182,13 @@ namespace Jellyfin.Server.Implementations.Tests.Data
Height = 1080,
BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
}
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"|",
- Array.Empty<ItemImageInfo>()
- };
+ Array.Empty<ItemImageInfo>());
+
+ return data;
}
[Theory]
@@ -239,30 +232,27 @@ namespace Jellyfin.Server.Implementations.Tests.Data
Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value));
}
- public static IEnumerable<object[]> DeserializeProviderIds_Valid_TestData()
+ public static TheoryData<string, Dictionary<string, string>> DeserializeProviderIds_Valid_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, Dictionary<string, string>>();
+
+ data.Add(
"Imdb=tt0119567",
new Dictionary<string, string>()
{
{ "Imdb", "tt0119567" },
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"Imdb=tt0119567|Tmdb=330|TmdbCollection=328",
new Dictionary<string, string>()
{
{ "Imdb", "tt0119567" },
{ "Tmdb", "330" },
{ "TmdbCollection", "328" },
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970",
new Dictionary<string, string>()
{
@@ -271,8 +261,9 @@ namespace Jellyfin.Server.Implementations.Tests.Data
{ "AudioDbArtist", "111352" },
{ "AudioDbAlbum", "2116560" },
{ "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" },
- }
- };
+ });
+
+ return data;
}
[Theory]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index 30e6542f9..d991f5574 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -1,10 +1,10 @@
+using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Server.Implementations.IO;
-using MediaBrowser.Model.System;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.IO
@@ -31,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Tests.IO
{
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
- if (MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows)
+ if (OperatingSystem.IsWindows())
{
var expectedWindowsPath = expectedAbsolutePath.Replace('/', '\\');
Assert.Equal(expectedWindowsPath, generatedPath.Split(':')[1]);
@@ -55,7 +55,7 @@ namespace Jellyfin.Server.Implementations.Tests.IO
[SkippableFact]
public void GetFileInfo_DanglingSymlink_ExistsFalse()
{
- Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ Skip.If(OperatingSystem.IsWindows());
string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link");
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index adbca8344..028ebdf55 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -6,11 +6,8 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
<RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
</PropertyGroup>
@@ -24,12 +21,12 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index c393742eb..362c3216f 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -1,4 +1,4 @@
-using System;
+using Emby.Naming.Common;
using Emby.Server.Implementations.Library.Resolvers.TV;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
@@ -14,22 +14,21 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
public class EpisodeResolverTest
{
+ private static readonly NamingOptions _namingOptions = new ();
+
[Fact]
public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode()
{
- var season = new Season { Name = "Season 1" };
var parent = new Folder { Name = "extras" };
- var libraryManagerMock = new Mock<ILibraryManager>();
- libraryManagerMock.Setup(x => x.GetItemById(It.IsAny<Guid>())).Returns(season);
- var episodeResolver = new EpisodeResolver(libraryManagerMock.Object);
+ var episodeResolver = new EpisodeResolver(_namingOptions);
var itemResolveArgs = new ItemResolveArgs(
Mock.Of<IServerApplicationPaths>(),
Mock.Of<IDirectoryService>())
{
Parent = parent,
CollectionType = CollectionType.TvShows,
- FileInfo = new FileSystemMetadata()
+ FileInfo = new FileSystemMetadata
{
FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
}
@@ -45,14 +44,14 @@ namespace Jellyfin.Server.Implementations.Tests.Library
// Have to create a mock because of moq proxies not being castable to a concrete implementation
// https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48
- var episodeResolver = new EpisodeResolverMock(Mock.Of<ILibraryManager>());
+ var episodeResolver = new EpisodeResolverMock(_namingOptions);
var itemResolveArgs = new ItemResolveArgs(
Mock.Of<IServerApplicationPaths>(),
Mock.Of<IDirectoryService>())
{
Parent = series,
CollectionType = CollectionType.TvShows,
- FileInfo = new FileSystemMetadata()
+ FileInfo = new FileSystemMetadata
{
FullName = "Extras/Extras S01E01.mkv"
}
@@ -62,7 +61,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
private class EpisodeResolverMock : EpisodeResolver
{
- public EpisodeResolverMock(ILibraryManager libraryManager) : base(libraryManager)
+ public EpisodeResolverMock(NamingOptions namingOptions) : base(namingOptions)
{
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
new file mode 100644
index 000000000..b29426d85
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
@@ -0,0 +1,232 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Naming.Common;
+using Emby.Server.Implementations.Library.Resolvers;
+using Emby.Server.Implementations.Library.Resolvers.Audio;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager;
+
+public class FindExtrasTests
+{
+ private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager;
+ private readonly Mock<IFileSystem> _fileSystemMock;
+
+ public FindExtrasTests()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ fixture.Register(() => new NamingOptions());
+ var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>();
+ configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ _fileSystemMock = fixture.Freeze<Mock<IFileSystem>>();
+ _fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path });
+ _libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>().Do(s => s.AddParts(
+ fixture.Create<IEnumerable<IResolverIgnoreRule>>(),
+ new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()), new AudioResolver(fixture.Create<NamingOptions>()) },
+ fixture.Create<IEnumerable<IIntroProvider>>(),
+ fixture.Create<IEnumerable<IBaseItemComparer>>(),
+ fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
+ .Create();
+
+ // This is pretty terrible but unavoidable
+ BaseItem.FileSystem ??= fixture.Create<IFileSystem>();
+ BaseItem.MediaSourceManager ??= fixture.Create<IMediaSourceManager>();
+ }
+
+ [Fact]
+ public void FindExtras_SeparateMovieFolder_FindsCorrectExtras()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up.mkv",
+ "/movies/Up/Up - trailer.mkv",
+ "/movies/Up/Up - sample.mkv",
+ "/movies/Up/Up something else.mkv"
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ IsDirectory = false
+ }).ToList();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ Assert.Equal(2, extras.Count);
+ Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(ExtraType.Sample, extras[1].ExtraType);
+ }
+
+ [Fact]
+ public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsCorrectExtras()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up.mkv",
+ "/movies/Up/Up - trailer.mkv",
+ "/movies/Up/trailers",
+ "/movies/Up/theme-music",
+ "/movies/Up/theme.mp3",
+ "/movies/Up/not a theme.mp3",
+ "/movies/Up/behind the scenes",
+ "/movies/Up/behind the scenes.mkv",
+ "/movies/Up/Up - sample.mkv",
+ "/movies/Up/Up something else.mkv"
+ };
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/trailers",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new ()
+ {
+ FullName = "/movies/Up/trailers/some trailer.mkv",
+ Name = "some trailer.mkv",
+ IsDirectory = false
+ }
+ });
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/behind the scenes",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new ()
+ {
+ FullName = "/movies/Up/behind the scenes/the making of Up.mkv",
+ Name = "the making of Up.mkv",
+ IsDirectory = false
+ }
+ });
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/theme-music",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new ()
+ {
+ FullName = "/movies/Up/theme-music/theme2.mp3",
+ Name = "theme2.mp3",
+ IsDirectory = false
+ }
+ });
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ Name = Path.GetFileName(p),
+ IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p))
+ }).ToList();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ Assert.Equal(6, extras.Count);
+ Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(ExtraType.Trailer, extras[1].ExtraType);
+ Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType);
+ Assert.Equal(ExtraType.Sample, extras[3].ExtraType);
+ Assert.Equal(ExtraType.ThemeSong, extras[4].ExtraType);
+ Assert.Equal(ExtraType.ThemeSong, extras[5].ExtraType);
+ }
+
+ [Fact]
+ public void FindExtras_SeparateMovieFolderWithMixedExtras_FindsOnlyExtrasInMovieFolder()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up.mkv",
+ "/movies/Up/trailer.mkv",
+ "/movies/Another Movie/trailer.mkv"
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ IsDirectory = false
+ }).ToList();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ Assert.Single(extras);
+ Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
+ Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
+ }
+
+ [Fact]
+ public void FindExtras_SeparateMovieFolderWithParts_FindsCorrectExtras()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up - part1.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up - part1.mkv",
+ "/movies/Up/Up - part2.mkv",
+ "/movies/Up/trailer.mkv",
+ "/movies/Another Movie/trailer.mkv"
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ IsDirectory = false
+ }).ToList();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ Assert.Single(extras);
+ Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
+ Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
+ }
+
+ [Fact]
+ public void FindExtras_SeriesWithTrailers_FindsCorrectExtras()
+ {
+ var owner = new Series { Name = "Dexter", Path = "/series/Dexter" };
+ var paths = new List<string>
+ {
+ "/series/Dexter/Season 1/S01E01.mkv",
+ "/series/Dexter/trailer.mkv",
+ "/series/Dexter/trailers/trailer2.mkv",
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p))
+ }).ToList();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ Assert.Equal(2, extras.Count);
+ Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
+ Assert.Equal("/series/Dexter/trailer.mkv", extras[0].Path);
+ Assert.Equal("/series/Dexter/trailers/trailer2.mkv", extras[1].Path);
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs
new file mode 100644
index 000000000..8ed3d8b94
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs
@@ -0,0 +1,32 @@
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.IO;
+using Emby.Server.Implementations.Library;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.MediaInfo;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library
+{
+ public class MediaSourceManagerTests
+ {
+ private readonly MediaSourceManager _mediaSourceManager;
+
+ public MediaSourceManagerTests()
+ {
+ IFixture fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ fixture.Inject<IFileSystem>(fixture.Create<ManagedFileSystem>());
+ _mediaSourceManager = fixture.Create<MediaSourceManager>();
+ }
+
+ [Theory]
+ [InlineData(@"C:\mydir\myfile.ext", MediaProtocol.File)]
+ [InlineData("/mydir/myfile.ext", MediaProtocol.File)]
+ [InlineData("file:///mydir/myfile.ext", MediaProtocol.File)]
+ [InlineData("http://example.com/stream.m3u8", MediaProtocol.Http)]
+ [InlineData("https://example.com/stream.m3u8", MediaProtocol.Http)]
+ [InlineData("rtsp://media.example.com:554/twister/audiotrack", MediaProtocol.Rtsp)]
+ public void GetPathProtocol_ValidArg_Correct(string path, MediaProtocol expected)
+ => Assert.Equal(expected, _mediaSourceManager.GetPathProtocol(path));
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index c5cc056f5..54a63a5f2 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -11,6 +11,18 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son", "imdbid", null)]
[InlineData("Superman: Red Son", "something", null)]
+ [InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")]
+ [InlineData("[tmdbid=618355]", "tmdbid", "618355")]
+ [InlineData("tmdbid=111111][tmdbid=618355]", "tmdbid", "618355")]
+ [InlineData("[tmdbid=618355]tmdbid=111111]", "tmdbid", "618355")]
+ [InlineData("tmdbid=618355]", "tmdbid", null)]
+ [InlineData("[tmdbid=618355", "tmdbid", null)]
+ [InlineData("tmdbid=618355", "tmdbid", null)]
+ [InlineData("tmdbid=", "tmdbid", null)]
+ [InlineData("tmdbid", "tmdbid", null)]
+ [InlineData("[tmdbid=][imdbid=tt10985510]", "tmdbid", null)]
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
{
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
index e8b93b437..09aec82b0 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/RecordingHelperTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using Emby.Server.Implementations.LiveTv.EmbyTV;
using MediaBrowser.Controller.LiveTv;
using Xunit;
@@ -8,43 +7,36 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
{
public static class RecordingHelperTests
{
- public static IEnumerable<object[]> GetRecordingName_Success_TestData()
+ public static TheoryData<string, TimerInfo> GetRecordingName_Success_TestData()
{
- yield return new object[]
- {
+ var data = new TheoryData<string, TimerInfo>();
+
+ data.Add(
"The Incredibles 2020_04_20_21_06_00",
new TimerInfo
{
Name = "The Incredibles",
StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local),
IsMovie = true
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"The Incredibles (2004)",
new TimerInfo
{
Name = "The Incredibles",
IsMovie = true,
ProductionYear = 2004
- }
- };
-
- yield return new object[]
- {
+ });
+ data.Add(
"The Big Bang Theory 2020_04_20_21_06_00",
new TimerInfo
{
Name = "The Big Bang Theory",
StartDate = new DateTime(2020, 4, 20, 21, 6, 0, DateTimeKind.Local),
IsProgramSeries = true,
- }
- };
-
- yield return new object[]
- {
+ });
+ data.Add(
"The Big Bang Theory S12E10",
new TimerInfo
{
@@ -52,11 +44,8 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
IsProgramSeries = true,
SeasonNumber = 12,
EpisodeNumber = 10
- }
- };
-
- yield return new object[]
- {
+ });
+ data.Add(
"The Big Bang Theory S12E10 The VCR Illumination",
new TimerInfo
{
@@ -65,34 +54,27 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
SeasonNumber = 12,
EpisodeNumber = 10,
EpisodeTitle = "The VCR Illumination"
- }
- };
-
- yield return new object[]
- {
+ });
+ data.Add(
"The Big Bang Theory 2018-12-06",
new TimerInfo
{
Name = "The Big Bang Theory",
IsProgramSeries = true,
- OriginalAirDate = new DateTime(2018, 12, 6)
- }
- };
+ OriginalAirDate = new DateTime(2018, 12, 6, 0, 0, 0, DateTimeKind.Local)
+ });
- yield return new object[]
- {
+ data.Add(
"The Big Bang Theory 2018-12-06 - The VCR Illumination",
new TimerInfo
{
Name = "The Big Bang Theory",
IsProgramSeries = true,
- OriginalAirDate = new DateTime(2018, 12, 6),
+ OriginalAirDate = new DateTime(2018, 12, 6, 0, 0, 0, DateTimeKind.Local),
EpisodeTitle = "The VCR Illumination"
- }
- };
+ });
- yield return new object[]
- {
+ data.Add(
"The Big Bang Theory 2018_12_06_21_06_00 - The VCR Illumination",
new TimerInfo
{
@@ -101,8 +83,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
IsProgramSeries = true,
OriginalAirDate = new DateTime(2018, 12, 6),
EpisodeTitle = "The VCR Illumination"
- }
- };
+ });
+
+ return data;
}
[Theory]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
new file mode 100644
index 000000000..3b3e38bd1
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
+using Jellyfin.Extensions.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
+{
+ public class SchedulesDirectDeserializeTests
+ {
+ private readonly JsonSerializerOptions _jsonOptions;
+
+ public SchedulesDirectDeserializeTests()
+ {
+ _jsonOptions = JsonDefaults.Options;
+ }
+
+ /// <summary>
+ /// /token reponse.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Token_Response_Live_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_live_response.json");
+ var tokenDto = JsonSerializer.Deserialize<TokenDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(tokenDto);
+ Assert.Equal(0, tokenDto!.Code);
+ Assert.Equal("OK", tokenDto.Message);
+ Assert.Equal("AWS-SD-web.1", tokenDto.ServerId);
+ Assert.Equal(new DateTime(2016, 08, 23, 13, 55, 25, DateTimeKind.Utc), tokenDto.TokenTimestamp);
+ Assert.Equal("f3fca79989cafe7dead71beefedc812b", tokenDto.Token);
+ }
+
+ /// <summary>
+ /// /token response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Token_Response_Offline_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_offline_response.json");
+ var tokenDto = JsonSerializer.Deserialize<TokenDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(tokenDto);
+ Assert.Equal(3_000, tokenDto!.Code);
+ Assert.Equal("Server offline for maintenance.", tokenDto.Message);
+ Assert.Equal("20141201.web.1", tokenDto.ServerId);
+ Assert.Equal(new DateTime(2015, 04, 23, 00, 03, 32, DateTimeKind.Utc), tokenDto.TokenTimestamp);
+ Assert.Equal("CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE", tokenDto.Token);
+ Assert.Equal("SERVICE_OFFLINE", tokenDto.Response);
+ }
+
+ /// <summary>
+ /// /schedules request.
+ /// </summary>
+ [Fact]
+ public void Serialize_Schedule_Request_Success()
+ {
+ var expectedString = File.ReadAllText("Test Data/SchedulesDirect/schedules_request.json").Trim();
+
+ var requestObject = new RequestScheduleForChannelDto[]
+ {
+ new RequestScheduleForChannelDto
+ {
+ StationId = "20454",
+ Date = new[]
+ {
+ "2015-03-13",
+ "2015-03-17"
+ }
+ },
+ new RequestScheduleForChannelDto
+ {
+ StationId = "10021",
+ Date = new[]
+ {
+ "2015-03-12",
+ "2015-03-13"
+ }
+ }
+ };
+
+ var requestString = JsonSerializer.Serialize(requestObject, _jsonOptions);
+ Assert.Equal(expectedString, requestString);
+ }
+
+ /// <summary>
+ /// /schedules response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Schedule_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/schedules_response.json");
+ var days = JsonSerializer.Deserialize<IReadOnlyList<DayDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(days);
+ Assert.Equal(1, days!.Count);
+
+ var dayDto = days[0];
+ Assert.Equal("20454", dayDto.StationId);
+ Assert.Equal(2, dayDto.Programs.Count);
+
+ Assert.Equal("SH005371070000", dayDto.Programs[0].ProgramId);
+ Assert.Equal(new DateTime(2015, 03, 03, 00, 00, 00, DateTimeKind.Utc), dayDto.Programs[0].AirDateTime);
+ Assert.Equal(1_800, dayDto.Programs[0].Duration);
+ Assert.Equal("Sy8HEMBPcuiAx3FBukUhKQ", dayDto.Programs[0].Md5);
+ Assert.True(dayDto.Programs[0].New);
+ Assert.Equal(2, dayDto.Programs[0].AudioProperties.Count);
+ Assert.Equal("stereo", dayDto.Programs[0].AudioProperties[0]);
+ Assert.Equal("cc", dayDto.Programs[0].AudioProperties[1]);
+ Assert.Equal(1, dayDto.Programs[0].VideoProperties.Count);
+ Assert.Equal("hdtv", dayDto.Programs[0].VideoProperties[0]);
+ }
+
+ /// <summary>
+ /// /programs response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Program_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/programs_response.json");
+ var programDtos = JsonSerializer.Deserialize<IReadOnlyList<ProgramDetailsDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(programDtos);
+ Assert.Equal(2, programDtos!.Count);
+ Assert.Equal("EP000000060003", programDtos[0].ProgramId);
+ Assert.Equal(1, programDtos[0].Titles.Count);
+ Assert.Equal("'Allo 'Allo!", programDtos[0].Titles[0].Title120);
+ Assert.Equal("Series", programDtos[0].EventDetails?.SubType);
+ Assert.Equal("en", programDtos[0].Descriptions?.Description1000[0].DescriptionLanguage);
+ Assert.Equal("A disguised British Intelligence officer is sent to help the airmen.", programDtos[0].Descriptions?.Description1000[0].Description);
+ Assert.Equal(new DateTime(1985, 11, 04), programDtos[0].OriginalAirDate);
+ Assert.Equal(1, programDtos[0].Genres.Count);
+ Assert.Equal("Sitcom", programDtos[0].Genres[0]);
+ Assert.Equal("The Poloceman Cometh", programDtos[0].EpisodeTitle150);
+ Assert.Equal(2, programDtos[0].Metadata[0].Gracenote?.Season);
+ Assert.Equal(3, programDtos[0].Metadata[0].Gracenote?.Episode);
+ Assert.Equal(13, programDtos[0].Cast.Count);
+ Assert.Equal("383774", programDtos[0].Cast[0].PersonId);
+ Assert.Equal("392649", programDtos[0].Cast[0].NameId);
+ Assert.Equal("Gorden Kaye", programDtos[0].Cast[0].Name);
+ Assert.Equal("Actor", programDtos[0].Cast[0].Role);
+ Assert.Equal("01", programDtos[0].Cast[0].BillingOrder);
+ Assert.Equal(3, programDtos[0].Crew.Count);
+ Assert.Equal("354407", programDtos[0].Crew[0].PersonId);
+ Assert.Equal("363281", programDtos[0].Crew[0].NameId);
+ Assert.Equal("David Croft", programDtos[0].Crew[0].Name);
+ Assert.Equal("Director", programDtos[0].Crew[0].Role);
+ Assert.Equal("01", programDtos[0].Crew[0].BillingOrder);
+ }
+
+ /// <summary>
+ /// /metadata/programs response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Metadata_Programs_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/metadata_programs_response.json");
+ var showImagesDtos = JsonSerializer.Deserialize<IReadOnlyList<ShowImagesDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(showImagesDtos);
+ Assert.Equal(1, showImagesDtos!.Count);
+ Assert.Equal("SH00712240", showImagesDtos[0].ProgramId);
+ Assert.Equal(4, showImagesDtos[0].Data.Count);
+ Assert.Equal("135", showImagesDtos[0].Data[0].Width);
+ Assert.Equal("180", showImagesDtos[0].Data[0].Height);
+ Assert.Equal("assets/p282288_b_v2_aa.jpg", showImagesDtos[0].Data[0].Uri);
+ Assert.Equal("Sm", showImagesDtos[0].Data[0].Size);
+ Assert.Equal("3x4", showImagesDtos[0].Data[0].Aspect);
+ Assert.Equal("Banner-L3", showImagesDtos[0].Data[0].Category);
+ Assert.Equal("yes", showImagesDtos[0].Data[0].Text);
+ Assert.Equal("true", showImagesDtos[0].Data[0].Primary);
+ Assert.Equal("Series", showImagesDtos[0].Data[0].Tier);
+ }
+
+ /// <summary>
+ /// /headends response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Headends_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/headends_response.json");
+ var headendsDtos = JsonSerializer.Deserialize<IReadOnlyList<HeadendsDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(headendsDtos);
+ Assert.Equal(8, headendsDtos!.Count);
+ Assert.Equal("CA00053", headendsDtos[0].Headend);
+ Assert.Equal("Cable", headendsDtos[0].Transport);
+ Assert.Equal("Beverly Hills", headendsDtos[0].Location);
+ Assert.Equal(2, headendsDtos[0].Lineups.Count);
+ Assert.Equal("Time Warner Cable - Cable", headendsDtos[0].Lineups[0].Name);
+ Assert.Equal("USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Lineup);
+ Assert.Equal("/20141201/lineups/USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Uri);
+ }
+
+ /// <summary>
+ /// /lineups response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Lineups_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineups_response.json");
+ var lineupsDto = JsonSerializer.Deserialize<LineupsDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(lineupsDto);
+ Assert.Equal(0, lineupsDto!.Code);
+ Assert.Equal("20141201.web.1", lineupsDto.ServerId);
+ Assert.Equal(new DateTime(2015, 04, 17, 14, 22, 17, DateTimeKind.Utc), lineupsDto.LineupTimestamp);
+ Assert.Equal(5, lineupsDto.Lineups.Count);
+ Assert.Equal("GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Lineup);
+ Assert.Equal("Freeview - Carlton - LWT (Southeast)", lineupsDto.Lineups[0].Name);
+ Assert.Equal("DVB-T", lineupsDto.Lineups[0].Transport);
+ Assert.Equal("London", lineupsDto.Lineups[0].Location);
+ Assert.Equal("/20141201/lineups/GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Uri);
+
+ Assert.Equal("DELETED LINEUP", lineupsDto.Lineups[4].Name);
+ Assert.True(lineupsDto.Lineups[4].IsDeleted);
+ }
+
+ /// <summary>
+ /// /lineup/:id response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Lineup_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineup_response.json");
+ var channelDto = JsonSerializer.Deserialize<ChannelDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(channelDto);
+ Assert.Equal(2, channelDto!.Map.Count);
+ Assert.Equal("24326", channelDto.Map[0].StationId);
+ Assert.Equal("001", channelDto.Map[0].Channel);
+ Assert.Equal("BBC ONE South", channelDto.Map[0].ProvderCallsign);
+ Assert.Equal("1", channelDto.Map[0].LogicalChannelNumber);
+ Assert.Equal("providerCallsign", channelDto.Map[0].MatchType);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
new file mode 100644
index 000000000..3e7d6ed1d
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.Localization;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Localization
+{
+ public class LocalizationManagerTests
+ {
+ [Fact]
+ public void GetCountries_All_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration
+ {
+ UICulture = "de-DE"
+ });
+ var countries = localizationManager.GetCountries().ToList();
+
+ Assert.Equal(139, countries.Count);
+
+ var germany = countries.FirstOrDefault(x => x.Name.Equals("DE", StringComparison.Ordinal));
+ Assert.NotNull(germany);
+ Assert.Equal("Germany", germany!.DisplayName);
+ Assert.Equal("DEU", germany.ThreeLetterISORegionName);
+ Assert.Equal("DE", germany.TwoLetterISORegionName);
+ }
+
+ [Fact]
+ public async Task GetCultures_All_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration
+ {
+ UICulture = "de-DE"
+ });
+ await localizationManager.LoadAll();
+ var cultures = localizationManager.GetCultures().ToList();
+
+ Assert.Equal(190, cultures.Count);
+
+ var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal));
+ Assert.NotNull(germany);
+ Assert.Equal("ger", germany!.ThreeLetterISOLanguageName);
+ Assert.Equal("German", germany.DisplayName);
+ Assert.Equal("German", germany.Name);
+ Assert.Contains("deu", germany.ThreeLetterISOLanguageNames);
+ Assert.Contains("ger", germany.ThreeLetterISOLanguageNames);
+ }
+
+ [Theory]
+ [InlineData("de")]
+ [InlineData("ger")]
+ [InlineData("german")]
+ public async Task FindLanguageInfo_Valid_Success(string identifier)
+ {
+ var localizationManager = Setup(new ServerConfiguration
+ {
+ UICulture = "de-DE"
+ });
+ await localizationManager.LoadAll();
+
+ var germany = localizationManager.FindLanguageInfo(identifier);
+ Assert.NotNull(germany);
+
+ Assert.Equal("ger", germany!.ThreeLetterISOLanguageName);
+ Assert.Equal("German", germany.DisplayName);
+ Assert.Equal("German", germany.Name);
+ Assert.Contains("deu", germany.ThreeLetterISOLanguageNames);
+ Assert.Contains("ger", germany.ThreeLetterISOLanguageNames);
+ }
+
+ [Fact]
+ public async Task GetParentalRatings_Default_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration
+ {
+ UICulture = "de-DE"
+ });
+ await localizationManager.LoadAll();
+ var ratings = localizationManager.GetParentalRatings().ToList();
+
+ Assert.Equal(23, ratings.Count);
+
+ var tvma = ratings.FirstOrDefault(x => x.Name.Equals("TV-MA", StringComparison.Ordinal));
+ Assert.NotNull(tvma);
+ Assert.Equal(9, tvma!.Value);
+ }
+
+ [Fact]
+ public async Task GetParentalRatings_ConfiguredCountryCode_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ MetadataCountryCode = "DE"
+ });
+ await localizationManager.LoadAll();
+ var ratings = localizationManager.GetParentalRatings().ToList();
+
+ Assert.Equal(10, ratings.Count);
+
+ var fsk = ratings.FirstOrDefault(x => x.Name.Equals("FSK-12", StringComparison.Ordinal));
+ Assert.NotNull(fsk);
+ Assert.Equal(7, fsk!.Value);
+ }
+
+ [Theory]
+ [InlineData("CA-R", "CA", 10)]
+ [InlineData("FSK-16", "DE", 8)]
+ [InlineData("FSK-18", "DE", 9)]
+ [InlineData("FSK-18", "US", 9)]
+ [InlineData("TV-MA", "US", 9)]
+ [InlineData("XXX", "asdf", 100)]
+ [InlineData("Germany: FSK-18", "DE", 9)]
+ public async Task GetRatingLevel_GivenValidString_Success(string value, string countryCode, int expectedLevel)
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ MetadataCountryCode = countryCode
+ });
+ await localizationManager.LoadAll();
+ var level = localizationManager.GetRatingLevel(value);
+ Assert.NotNull(level);
+ Assert.Equal(expectedLevel, level!);
+ }
+
+ [Fact]
+ public async Task GetRatingLevel_GivenUnratedString_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ UICulture = "de-DE"
+ });
+ await localizationManager.LoadAll();
+ Assert.Null(localizationManager.GetRatingLevel("n/a"));
+ }
+
+ [Theory]
+ [InlineData("Default", "Default")]
+ [InlineData("HeaderLiveTV", "Live TV")]
+ public void GetLocalizedString_Valid_Success(string key, string expected)
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ UICulture = "en-US"
+ });
+
+ var translated = localizationManager.GetLocalizedString(key);
+ Assert.NotNull(translated);
+ Assert.Equal(expected, translated);
+ }
+
+ [Fact]
+ public void GetLocalizedString_Invalid_Success()
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ UICulture = "en-US"
+ });
+
+ var key = "SuperInvalidTranslationKeyThatWillNeverBeAdded";
+
+ var translated = localizationManager.GetLocalizedString(key);
+ Assert.NotNull(translated);
+ Assert.Equal(key, translated);
+ }
+
+ private LocalizationManager Setup(ServerConfiguration config)
+ {
+ var mockConfiguration = new Mock<IServerConfigurationManager>();
+ mockConfiguration.SetupGet(x => x.Configuration).Returns(config);
+
+ return new LocalizationManager(mockConfiguration.Object, new NullLogger<LocalizationManager>());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs
new file mode 100644
index 000000000..28d832ef8
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.QuickConnect;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.QuickConnect
+{
+ public class QuickConnectManagerTests
+ {
+ private static readonly AuthorizationInfo _quickConnectAuthInfo = new AuthorizationInfo
+ {
+ Device = "Device",
+ DeviceId = "DeviceId",
+ Client = "Client",
+ Version = "1.0.0"
+ };
+
+ private readonly Fixture _fixture;
+ private readonly ServerConfiguration _config;
+ private readonly QuickConnectManager _quickConnectManager;
+
+ public QuickConnectManagerTests()
+ {
+ _config = new ServerConfiguration();
+ var configManager = new Mock<IServerConfigurationManager>();
+ configManager.Setup(x => x.Configuration).Returns(_config);
+
+ _fixture = new Fixture();
+ _fixture.Customize(new AutoMoqCustomization
+ {
+ ConfigureMembers = true
+ }).Inject(configManager.Object);
+
+ // User object contains circular references.
+ _fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
+ .ForEach(b => _fixture.Behaviors.Remove(b));
+ _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
+
+ _quickConnectManager = _fixture.Create<QuickConnectManager>();
+ }
+
+ [Fact]
+ public void IsEnabled_QuickConnectUnavailable_False()
+ => Assert.False(_quickConnectManager.IsEnabled);
+
+ [Theory]
+ [InlineData("", "DeviceId", "Client", "1.0.0")]
+ [InlineData("Device", "", "Client", "1.0.0")]
+ [InlineData("Device", "DeviceId", "", "1.0.0")]
+ [InlineData("Device", "DeviceId", "Client", "")]
+ public void TryConnect_InvalidAuthorizationInfo_ThrowsArgumentException(string device, string deviceId, string client, string version)
+ => Assert.Throws<ArgumentException>(() => _quickConnectManager.TryConnect(
+ new AuthorizationInfo
+ {
+ Device = device,
+ DeviceId = deviceId,
+ Client = client,
+ Version = version
+ }));
+
+ [Fact]
+ public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
+ => Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
+
+ [Fact]
+ public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
+ => Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
+
+ [Fact]
+ public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
+ => Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
+
+ [Fact]
+ public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
+ => Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
+
+ [Fact]
+ public void IsEnabled_QuickConnectAvailable_True()
+ {
+ _config.QuickConnectAvailable = true;
+ Assert.True(_quickConnectManager.IsEnabled);
+ }
+
+ [Fact]
+ public void CheckRequestStatus_QuickConnectAvailable_Success()
+ {
+ _config.QuickConnectAvailable = true;
+ var res1 = _quickConnectManager.TryConnect(_quickConnectAuthInfo);
+ var res2 = _quickConnectManager.CheckRequestStatus(res1.Secret);
+ Assert.Equal(res1, res2);
+ }
+
+ [Fact]
+ public void CheckRequestStatus_UnknownSecret_ThrowsResourceNotFoundException()
+ {
+ _config.QuickConnectAvailable = true;
+ Assert.Throws<ResourceNotFoundException>(() => _quickConnectManager.CheckRequestStatus("Unknown secret"));
+ }
+
+ [Fact]
+ public void GetAuthorizedRequest_UnknownSecret_ThrowsResourceNotFoundException()
+ {
+ _config.QuickConnectAvailable = true;
+ Assert.Throws<ResourceNotFoundException>(() => _quickConnectManager.GetAuthorizedRequest("Unknown secret"));
+ }
+
+ [Fact]
+ public async Task AuthorizeRequest_QuickConnectAvailable_Success()
+ {
+ _config.QuickConnectAvailable = true;
+ var res = _quickConnectManager.TryConnect(_quickConnectAuthInfo);
+ Assert.True(await _quickConnectManager.AuthorizeRequest(Guid.Empty, res.Code));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs
new file mode 100644
index 000000000..59d82678e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Sorting/AiredEpisodeOrderComparerTests.cs
@@ -0,0 +1,164 @@
+using System;
+using Emby.Server.Implementations.Sorting;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Sorting
+{
+ public class AiredEpisodeOrderComparerTests
+ {
+ [Theory]
+ [ClassData(typeof(EpisodeBadData))]
+ public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
+ {
+ var cmp = new AiredEpisodeOrderComparer();
+ Assert.Throws<ArgumentNullException>(() => cmp.Compare(x, y));
+ }
+
+ [Theory]
+ [ClassData(typeof(EpisodeTestData))]
+ public void AiredEpisodeOrderCompareTest(BaseItem x, BaseItem y, int expected)
+ {
+ var cmp = new AiredEpisodeOrderComparer();
+
+ Assert.Equal(expected, cmp.Compare(x, y));
+ Assert.Equal(-expected, cmp.Compare(y, x));
+ }
+
+ private class EpisodeBadData : TheoryData<BaseItem?, BaseItem?>
+ {
+ public EpisodeBadData()
+ {
+ Add(null, new Episode());
+ Add(new Episode(), null);
+ }
+ }
+
+ private class EpisodeTestData : TheoryData<BaseItem, BaseItem, int>
+ {
+ public EpisodeTestData()
+ {
+ Add(
+ new Movie(),
+ new Movie(),
+ 0);
+
+ Add(
+ new Movie(),
+ new Episode(),
+ 1);
+
+ // Good cases
+ Add(
+ new Episode(),
+ new Episode(),
+ 0);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 0);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 2, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1);
+
+ // Good Specials
+ Add(
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 0);
+
+ Add(
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1);
+
+ // Specials to Episodes
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 2 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 3, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 3, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsAfterSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 2 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 0);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 3 },
+ new Episode { ParentIndexNumber = 0, IndexNumber = 1, AirsBeforeSeasonNumber = 1, AirsBeforeEpisodeNumber = 2 },
+ 1);
+
+ // Premiere Date
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 12, 0, 0, 0) },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 12, 0, 0, 0) },
+ 0);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 11, 0, 0, 0) },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 12, 0, 0, 0) },
+ -1);
+
+ Add(
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 12, 0, 0, 0) },
+ new Episode { ParentIndexNumber = 1, IndexNumber = 1, PremiereDate = new DateTime(2021, 09, 11, 0, 0, 0) },
+ 1);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json
new file mode 100644
index 000000000..015afeecc
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json
@@ -0,0 +1 @@
+[{"headend":"CA00053","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Time Warner Cable - Cable","lineup":"USA-CA00053-DEFAULT","uri":"/20141201/lineups/USA-CA00053-DEFAULT"},{"name":"Time Warner Cable - Digital","lineup":"USA-CA00053-X","uri":"/20141201/lineups/USA-CA00053-X"}]},{"headend":"CA61222","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Mulholland Estates - Cable","lineup":"USA-CA61222-DEFAULT","uri":"/20141201/lineups/USA-CA61222-DEFAULT"}]},{"headend":"CA66511","transport":"Cable","location":"Los Angeles","lineups":[{"name":"AT&T U-verse TV - Digital","lineup":"USA-CA66511-X","uri":"/20141201/lineups/USA-CA66511-X"}]},{"headend":"CA67309","transport":"Cable","location":"Westchester","lineups":[{"name":"Time Warner Cable Sherman Oaks - Cable","lineup":"USA-CA67309-DEFAULT","uri":"/20141201/lineups/USA-CA67309-DEFAULT"},{"name":"Time Warner Cable Sherman Oaks - Digital","lineup":"USA-CA67309-X","uri":"/20141201/lineups/USA-CA67309-X"}]},{"headend":"CA67310","transport":"Cable","location":"Eagle Rock","lineups":[{"name":"Time Warner Cable City of Los Angeles - Cable","lineup":"USA-CA67310-DEFAULT","uri":"/20141201/lineups/USA-CA67310-DEFAULT"},{"name":"Time Warner Cable City of Los Angeles - Digital","lineup":"USA-CA67310-X","uri":"/20141201/lineups/USA-CA67310-X"}]},{"headend":"DISH803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DISH Los Angeles - Satellite","lineup":"USA-DISH803-DEFAULT","uri":"/20141201/lineups/USA-DISH803-DEFAULT"}]},{"headend":"DITV803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DIRECTV Los Angeles - Satellite","lineup":"USA-DITV803-DEFAULT","uri":"/20141201/lineups/USA-DITV803-DEFAULT"}]},{"headend":"90210","transport":"Antenna","location":"90210","lineups":[{"name":"Antenna","lineup":"USA-OTA-90210","uri":"/20141201/lineups/USA-OTA-90210"}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json
new file mode 100644
index 000000000..072089470
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json
@@ -0,0 +1 @@
+{"map":[{"stationID":"24326","channel":"001","providerCallsign":"BBC ONE South","logicalChannelNumber":"1","matchType":"providerCallsign"},{"stationID":"17154","channel":"002","providerCallsign":"BBC TWO","logicalChannelNumber":"2","matchType":"providerCallsign"}]}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json
new file mode 100644
index 000000000..032a84e59
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json
@@ -0,0 +1 @@
+{"code":0,"serverID":"20141201.web.1","datetime":"2015-04-17T14:22:17Z","lineups":[{"lineup":"GBR-0001317-DEFAULT","name":"Freeview - Carlton - LWT (Southeast)","transport":"DVB-T","location":"London","uri":"/20141201/lineups/GBR-0001317-DEFAULT"},{"lineup":"USA-IL57303-X","name":"Comcast Waukegan/Lake Forest Area - Digital","transport":"Cable","location":"Lake Forest","uri":"/20141201/lineups/USA-IL57303-X"},{"lineup":"USA-NY67791-X","name":"Verizon Fios Queens - Digital","transport":"Cable","location":"Fresh Meadows","uri":"/20141201/lineups/USA-NY67791-X"},{"lineup":"USA-OTA-60030","name":"Local Over the Air Broadcast","transport":"Antenna","location":"60030","uri":"/20141201/lineups/USA-OTA-60030"},{"lineup":"USA-WI61859-DEFAULT","name":"DELETED LINEUP","isDeleted":true}]}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json
new file mode 100644
index 000000000..78166f09a
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json
@@ -0,0 +1 @@
+[{"programID":"SH00712240","data":[{"width":"135","height":"180","uri":"assets/p282288_b_v2_aa.jpg","size":"Sm","aspect":"3x4","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"720","height":"540","uri":"assets/p282288_b_h6_aa.jpg","size":"Lg","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"960","height":"1440","uri":"assets/p282288_b_v8_aa.jpg","size":"Ms","aspect":"2x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"180","height":"135","uri":"assets/p282288_b_h5_aa.jpg","size":"Sm","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json
new file mode 100644
index 000000000..fe2a94436
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json
@@ -0,0 +1 @@
+[{"programID":"EP000000060003","titles":[{"title120":"'Allo 'Allo!"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"A disguised British Intelligence officer is sent to help the airmen."}]},"originalAirDate":"1985-11-04","genres":["Sitcom"],"episodeTitle150":"The Poloceman Cometh","metadata":[{"Gracenote":{"season":2,"episode":3}}],"cast":[{"personId":"383774","nameId":"392649","name":"Gorden Kaye","role":"Actor","billingOrder":"01"},{"personId":"246840","nameId":"250387","name":"Carmen Silvera","role":"Actor","billingOrder":"02"},{"personId":"376955","nameId":"385830","name":"Rose Hill","role":"Actor","billingOrder":"03"},{"personId":"259773","nameId":"263340","name":"Vicki Michelle","role":"Actor","billingOrder":"04"},{"personId":"353113","nameId":"361987","name":"Kirsten Cooke","role":"Actor","billingOrder":"05"},{"personId":"77787","nameId":"77787","name":"Richard Marner","role":"Actor","billingOrder":"06"},{"personId":"230921","nameId":"234193","name":"Guy Siner","role":"Actor","billingOrder":"07"},{"personId":"374934","nameId":"383809","name":"Kim Hartman","role":"Actor","billingOrder":"08"},{"personId":"369151","nameId":"378026","name":"Richard Gibson","role":"Actor","billingOrder":"09"},{"personId":"343690","nameId":"352564","name":"Arthur Bostrom","role":"Actor","billingOrder":"10"},{"personId":"352557","nameId":"361431","name":"John D. Collins","role":"Actor","billingOrder":"11"},{"personId":"605275","nameId":"627734","name":"Nicholas Frankau","role":"Actor","billingOrder":"12"},{"personId":"373394","nameId":"382269","name":"Jack Haig","role":"Actor","billingOrder":"13"}],"crew":[{"personId":"354407","nameId":"363281","name":"David Croft","role":"Director","billingOrder":"01"},{"personId":"354407","nameId":"363281","name":"David Croft","role":"Writer","billingOrder":"02"},{"personId":"105145","nameId":"105145","name":"Jeremy Lloyd","role":"Writer","billingOrder":"03"}],"showType":"Series","hasImageArtwork":true,"md5":"Jo5NKxoo44xRvBCAq8QT2A"},{"programID":"EP000000510142","titles":[{"title120":"A Different World"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"Whitley and Dwayne tell new students about their honeymoon in Los Angeles."}]},"originalAirDate":"1992-09-24","genres":["Sitcom"],"episodeTitle150":"Honeymoon in L.A.","metadata":[{"Gracenote":{"season":6,"episode":1}}],"cast":[{"personId":"700","nameId":"700","name":"Jasmine Guy","role":"Actor","billingOrder":"01"},{"personId":"729","nameId":"729","name":"Kadeem Hardison","role":"Actor","billingOrder":"02"},{"personId":"120","nameId":"120","name":"Darryl M. Bell","role":"Actor","billingOrder":"03"},{"personId":"1729","nameId":"1729","name":"Cree Summer","role":"Actor","billingOrder":"04"},{"personId":"217","nameId":"217","name":"Charnele Brown","role":"Actor","billingOrder":"05"},{"personId":"1811","nameId":"1811","name":"Glynn Turman","role":"Actor","billingOrder":"06"},{"personId":"1232","nameId":"1232","name":"Lou Myers","role":"Actor","billingOrder":"07"},{"personId":"1363","nameId":"1363","name":"Jada Pinkett","role":"Guest Star","billingOrder":"08"},{"personId":"222967","nameId":"225536","name":"Ajai Sanders","role":"Guest Star","billingOrder":"09"},{"personId":"181744","nameId":"183292","name":"Karen Malina White","role":"Guest Star","billingOrder":"10"},{"personId":"305017","nameId":"318897","name":"Patrick Y. Malone","role":"Guest Star","billingOrder":"11"},{"personId":"9841","nameId":"9841","name":"Bumper Robinson","role":"Guest Star","billingOrder":"12"},{"personId":"426422","nameId":"435297","name":"Sister Souljah","role":"Guest Star","billingOrder":"13"},{"personId":"25","nameId":"25","name":"Debbie Allen","role":"Guest Star","billingOrder":"14"},{"personId":"668","nameId":"668","name":"Gilbert Gottfried","role":"Guest Star","billingOrder":"15"}],"showType":"Series","hasImageArtwork":true,"md5":"P5kz0QmCeYxIA+yL0H4DWw"}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json
new file mode 100644
index 000000000..5ef1bfb1c
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json
@@ -0,0 +1 @@
+[{"stationID":"20454","date":["2015-03-13","2015-03-17"]},{"stationID":"10021","date":["2015-03-12","2015-03-13"]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json
new file mode 100644
index 000000000..4a97e5517
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json
@@ -0,0 +1 @@
+[{"stationID":"20454","programs":[{"programID":"SH005371070000","airDateTime":"2015-03-03T00:00:00Z","duration":1800,"md5":"Sy8HEMBPcuiAx3FBukUhKQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]},{"programID":"EP000014577244","airDateTime":"2015-03-03T00:30:00Z","duration":1800,"md5":"25DNXVXO192JI7Y9vSW9lQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json
new file mode 100644
index 000000000..e5fb64a6f
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json
@@ -0,0 +1 @@
+{"code":0,"message":"OK","serverID":"AWS-SD-web.1","datetime":"2016-08-23T13:55:25Z","token":"f3fca79989cafe7dead71beefedc812b"}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json
new file mode 100644
index 000000000..b66a4ed0c
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json
@@ -0,0 +1 @@
+{"response":"SERVICE_OFFLINE","code":3000,"serverID":"20141201.web.1","message":"Server offline for maintenance.","datetime":"2015-04-23T00:03:32Z","token":"CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE"}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip
new file mode 100644
index 000000000..15628e26b
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/empty.zip
Binary files differ
diff --git a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
new file mode 100644
index 000000000..31f33c682
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Linq;
+using Jellyfin.Data.Enums;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem
+{
+ public class BaseItemKindTests
+ {
+ public static TheoryData<Type> BaseItemKind_TestData()
+ {
+ var data = new TheoryData<Type>();
+
+ var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
+ foreach (var assembly in loadedAssemblies)
+ {
+ if (IsProjectAssemblyName(assembly.FullName))
+ {
+ var baseItemTypes = assembly.GetTypes()
+ .Where(targetType => targetType.IsClass
+ && !targetType.IsAbstract
+ && targetType.IsSubclassOf(typeof(MediaBrowser.Controller.Entities.BaseItem)));
+ foreach (var baseItemType in baseItemTypes)
+ {
+ data.Add(baseItemType);
+ }
+ }
+ }
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(BaseItemKind_TestData))]
+ public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemDescendantType)
+ {
+ var enumValue = Enum.Parse<BaseItemKind>(baseItemDescendantType.Name);
+ Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue));
+ }
+
+ [Theory]
+ [MemberData(nameof(BaseItemKind_TestData))]
+ public void GetBaseItemKind_WhenCalledAfterDefaultCtor_DoesNotThrow(Type baseItemDescendantType)
+ {
+ var defaultConstructor = baseItemDescendantType.GetConstructor(Type.EmptyTypes);
+ var instance = (MediaBrowser.Controller.Entities.BaseItem)defaultConstructor!.Invoke(null);
+ var exception = Record.Exception(() => instance.GetBaseItemKind());
+ Assert.Null(exception);
+ }
+
+ private static bool IsProjectAssemblyName(string? name)
+ {
+ if (name == null)
+ {
+ return false;
+ }
+
+ return name.StartsWith("Jellyfin", StringComparison.OrdinalIgnoreCase)
+ || name.StartsWith("Emby", StringComparison.OrdinalIgnoreCase)
+ || name.StartsWith("MediaBrowser", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
index 70acbfc40..7abd2e685 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
@@ -40,7 +40,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
_fixture.Customize(new AutoMoqCustomization
{
ConfigureMembers = true
- }).Inject(http);
+ });
+ _fixture.Inject(http);
_installationManager = _fixture.Create<InstallationManager>();
}
@@ -78,5 +79,32 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
packages = _installationManager.FilterPackages(packages, id: new Guid("a4df60c5-6ab4-412a-8f79-2cab93fb2bc5")).ToArray();
Assert.Single(packages);
}
+
+ [Fact]
+ public async Task InstallPackage_InvalidChecksum_ThrowsInvalidDataException()
+ {
+ var packageInfo = new InstallationInfo()
+ {
+ Name = "Test",
+ SourceUrl = "https://repo.jellyfin.org/releases/plugin/empty/empty.zip",
+ Checksum = "InvalidChecksum"
+ };
+
+ await Assert.ThrowsAsync<InvalidDataException>(() => _installationManager.InstallPackage(packageInfo, CancellationToken.None)).ConfigureAwait(false);
+ }
+
+ [Fact]
+ public async Task InstallPackage_Valid_Success()
+ {
+ var packageInfo = new InstallationInfo()
+ {
+ Name = "Test",
+ SourceUrl = "https://repo.jellyfin.org/releases/plugin/empty/empty.zip",
+ Checksum = "11b5b2f1a9ebc4f66d6ef19018543361"
+ };
+
+ var ex = await Record.ExceptionAsync(() => _installationManager.InstallPackage(packageInfo, CancellationToken.None)).ConfigureAwait(false);
+ Assert.Null(ex);
+ }
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
index ea6838682..21131eb97 100644
--- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -1,13 +1,13 @@
using System;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
using System.Net.Http.Headers;
-using System.Net.Mime;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Api.Models.UserDtos;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using Xunit;
namespace Jellyfin.Server.Integration.Tests
@@ -26,14 +26,13 @@ namespace Jellyfin.Server.Integration.Tests
using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode);
- using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(
+ using var content = JsonContent.Create(
new AuthenticateUserByName()
{
Username = user!.Name,
Pw = user.Password,
},
- jsonOptions));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ options: jsonOptions);
content.Headers.Add("X-Emby-Authorization", DummyAuthHeader);
using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index f5411dcb8..3396a94e5 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -5,7 +5,7 @@ using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using Xunit;
namespace Jellyfin.Server.Integration.Tests.Controllers
@@ -40,7 +40,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!);
- Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd());
+ Assert.Equal(await response.Content.ReadAsStringAsync().ConfigureAwait(false), await reader.ReadToEndAsync().ConfigureAwait(false));
}
[Fact]
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
new file mode 100644
index 000000000..5d7b0e874
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http.Json;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
+using MediaBrowser.Model.Dlna;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ public sealed class DlnaControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private const string NonExistentProfile = "1322f35b8f2c434dad3cc07c9b97dbd1";
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private static string? _accessToken;
+ private static string? _newDeviceProfileId;
+
+ public DlnaControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task GetProfile_DoesNotExist_NotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var getResponse = await client.GetAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task DeleteProfile_DoesNotExist_NotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var getResponse = await client.DeleteAsync("/Dlna/Profiles/" + NonExistentProfile).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task UpdateProfile_DoesNotExist_NotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var deviceProfile = new DeviceProfile()
+ {
+ Name = "ThisProfileDoesNotExist"
+ };
+
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
+ }
+
+ [Fact]
+ [Priority(1)]
+ public async Task CreateProfile_Valid_NoContent()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var deviceProfile = new DeviceProfile()
+ {
+ Name = "ThisProfileIsNew"
+ };
+
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ }
+
+ [Fact]
+ [Priority(2)]
+ public async Task GetProfileInfos_Valid_ContainsThisProfileIsNew()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var response = await client.GetAsync("/Dlna/ProfileInfos").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var profiles = await JsonSerializer.DeserializeAsync<DeviceProfileInfo[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ _jsonOptions).ConfigureAwait(false);
+
+ var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal));
+ Assert.NotNull(newProfile);
+ _newDeviceProfileId = newProfile!.Id;
+ }
+
+ [Fact]
+ [Priority(3)]
+ public async Task UpdateProfile_Valid_NoContent()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var updatedProfile = new DeviceProfile()
+ {
+ Name = "ThisProfileIsUpdated",
+ Id = _newDeviceProfileId
+ };
+
+ using var getResponse = await client.PostAsJsonAsync("/Dlna/Profiles", updatedProfile, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ }
+
+ [Fact]
+ [Priority(4)]
+ public async Task DeleteProfile_Valid_NoContent()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var getResponse = await client.DeleteAsync("/Dlna/Profiles/" + _newDeviceProfileId).ConfigureAwait(false);
+ Console.WriteLine(await getResponse.Content.ReadAsStringAsync().ConfigureAwait(false));
+ Assert.Equal(HttpStatusCode.NoContent, getResponse.StatusCode);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs
new file mode 100644
index 000000000..34d26680a
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs
@@ -0,0 +1,61 @@
+using System.Globalization;
+using System.Net;
+using System.Net.Mime;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class MediaInfoControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private static string? _accessToken;
+
+ public MediaInfoControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task BitrateTest_Default_Ok()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.GetAsync("Playback/BitrateTest").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
+ Assert.NotNull(response.Content.Headers.ContentLength);
+ }
+
+ [Theory]
+ [InlineData(102400)]
+ public async Task BitrateTest_WithValidParam_Ok(int size)
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
+ Assert.NotNull(response.Content.Headers.ContentLength);
+ Assert.InRange(response.Content.Headers.ContentLength!.Value, size, long.MaxValue);
+ }
+
+ [Theory]
+ [InlineData(0)] // Zero
+ [InlineData(-102400)] // Negative value
+ [InlineData(1000000000)] // Too large
+ public async Task BitrateTest_InvalidValue_BadRequest(int size)
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
new file mode 100644
index 000000000..24251013c
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.LibraryStructureDto;
+using Jellyfin.Extensions.Json;
+using MediaBrowser.Model.Configuration;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class MediaStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private static string? _accessToken;
+
+ public MediaStructureControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task RenameVirtualFolder_WhiteSpaceName_ReturnsBadRequest()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var postContent = new ByteArrayContent(Array.Empty<byte>());
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task RenameVirtualFolder_WhiteSpaceNewName_ReturnsBadRequest()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var postContent = new ByteArrayContent(Array.Empty<byte>());
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task RenameVirtualFolder_NameDoesntExist_ReturnsNotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var postContent = new ByteArrayContent(Array.Empty<byte>());
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task AddMediaPath_PathDoesntExist_ReturnsNotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var data = new MediaPathDto()
+ {
+ Name = "Test",
+ Path = "/this/path/doesnt/exist"
+ };
+
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task UpdateMediaPath_WhiteSpaceName_ReturnsBadRequest()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var data = new UpdateMediaPathRequestDto()
+ {
+ Name = " ",
+ PathInfo = new MediaPathInfo("test")
+ };
+
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions).ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task RemoveMediaPath_WhiteSpaceName_ReturnsBadRequest()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task RemoveMediaPath_PathDoesntExist_ReturnsNotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index 169a5a6c5..e72dacfe0 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -1,12 +1,12 @@
using System;
using System.Net;
using System.Net.Http;
-using System.Net.Http.Headers;
+using System.Net.Http.Json;
using System.Net.Mime;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using Xunit;
using Xunit.Priority;
@@ -36,9 +36,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PreferredMetadataLanguage = "nl"
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false);
+ using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false);
@@ -80,9 +78,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Password = "NewPassword"
};
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false);
+ var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions).ConfigureAwait(false);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 6584490de..588e25a82 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -3,12 +3,11 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Net.Mime;
+using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.UserDtos;
-using MediaBrowser.Common.Json;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using Xunit;
using Xunit.Priority;
@@ -31,18 +30,10 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
}
private Task<HttpResponseMessage> CreateUserByName(HttpClient httpClient, CreateUserByName request)
- {
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- return httpClient.PostAsync("Users/New", postContent);
- }
+ => httpClient.PostAsJsonAsync("Users/New", request, _jsonOpions);
private Task<HttpResponseMessage> UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request)
- {
- using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
- postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
- return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent);
- }
+ => httpClient.PostAsJsonAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", request, _jsonOpions);
[Fact]
[Priority(-1)]
diff --git a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
index 732b4f050..2361e4aa4 100644
--- a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
@@ -21,6 +21,7 @@ namespace Jellyfin.Server.Integration.Tests
[InlineData("a=1", "a=1")] // won't be processed as it has a value
[InlineData("a%3D1%26b%3D2%26c%3D3", "a=1&b=2&c=3")] // will be processed.
[InlineData("a=b&a=c", "a=b")]
+ [InlineData("a%3D1", "a=1")]
[InlineData("a%3Db%26a%3Dc", "a=b")]
public async Task Ensure_Decoding_Of_Urls_Is_Working(string sourceUrl, string unencodedUrl)
{
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 59f125cd0..a59900b02 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -1,10 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
@@ -12,14 +9,14 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.7" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
index d9ec81a27..3d34a18e7 100644
--- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using Emby.Server.Implementations;
-using Emby.Server.Implementations.IO;
using MediaBrowser.Common;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -44,10 +43,7 @@ namespace Jellyfin.Server.Integration.Tests
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
// Specify the startup command line options
- var commandLineOpts = new StartupOptions
- {
- NoWebClient = true
- };
+ var commandLineOpts = new StartupOptions();
// Use a temporary directory for the application paths
var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
@@ -70,7 +66,7 @@ namespace Jellyfin.Server.Integration.Tests
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
- var serviceCollection = new ServiceCollection();
+
_disposableComponents.Add(loggerFactory);
// Create the app host and initialize it
@@ -78,11 +74,10 @@ namespace Jellyfin.Server.Integration.Tests
appPaths,
loggerFactory,
commandLineOpts,
- new ConfigurationBuilder().Build(),
- new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
- serviceCollection);
+ new ConfigurationBuilder().Build());
_disposableComponents.Add(appHost);
- appHost.Init();
+ var serviceCollection = new ServiceCollection();
+ appHost.Init(serviceCollection);
// Configure the web host builder
Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
new file mode 100644
index 000000000..8c49a2e2b
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
@@ -0,0 +1,32 @@
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Middleware
+{
+ public sealed class RobotsRedirectionMiddlewareTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+
+ public RobotsRedirectionMiddlewareTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task RobotsDotTxtRedirects()
+ {
+ var client = _factory.CreateClient(
+ new WebApplicationFactoryClientOptions()
+ {
+ AllowAutoRedirect = false
+ });
+
+ var response = await client.GetAsync("robots.txt").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal("web/robots.txt", response.Headers.Location?.ToString());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
index 0a463cfa3..bf74efa09 100644
--- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
@@ -2,9 +2,7 @@ using System.Collections.Generic;
using System.Reflection;
using Emby.Server.Implementations;
using MediaBrowser.Controller;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Integration.Tests
@@ -21,22 +19,16 @@ namespace Jellyfin.Server.Integration.Tests
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
- /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
- /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
public TestAppHost(
IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
- IConfiguration startup,
- IFileSystem fileSystem,
- IServiceCollection collection)
+ IConfiguration startup)
: base(
applicationPaths,
loggerFactory,
options,
- startup,
- fileSystem,
- collection)
+ startup)
{
}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index c8e72c10d..ada9034df 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -1,11 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
@@ -13,13 +10,13 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.7" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 146b16cf9..a1bdfa31b 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -1,10 +1,12 @@
-using System.Globalization;
-using System.Text;
+using System;
+using System.Linq;
+using System.Net;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using Jellyfin.Server.Extensions;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
@@ -13,20 +15,63 @@ namespace Jellyfin.Server.Tests
{
public class ParseNetworkTests
{
- /// <summary>
- /// Order of the result has always got to be hosts, then networks.
- /// </summary>
- /// <param name="ip4">IP4 enabled.</param>
- /// <param name="ip6">IP6 enabled.</param>
- /// <param name="hostList">List to parse.</param>
- /// <param name="match">What it should match.</param>
+ public static TheoryData<bool, bool, string[], IPAddress[], IPNetwork[]> TestNetworks_TestData()
+ {
+ var data = new TheoryData<bool, bool, string[], IPAddress[], IPNetwork[]>();
+ data.Add(
+ true,
+ true,
+ new string[] { "192.168.t", "127.0.0.1", "1234.1232.12.1234" },
+ new IPAddress[] { IPAddress.Loopback.MapToIPv6() },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ false,
+ new string[] { "192.168.x", "127.0.0.1", "1234.1232.12.1234" },
+ new IPAddress[] { IPAddress.Loopback },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ true,
+ new string[] { "::1" },
+ Array.Empty<IPAddress>(),
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+
+ data.Add(
+ false,
+ false,
+ new string[] { "localhost" },
+ Array.Empty<IPAddress>(),
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ true,
+ false,
+ new string[] { "localhost" },
+ new IPAddress[] { IPAddress.Loopback },
+ Array.Empty<IPNetwork>());
+
+ data.Add(
+ false,
+ true,
+ new string[] { "localhost" },
+ Array.Empty<IPAddress>(),
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+
+ data.Add(
+ true,
+ true,
+ new string[] { "localhost" },
+ new IPAddress[] { IPAddress.Loopback.MapToIPv6() },
+ new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ return data;
+ }
+
[Theory]
- // [InlineData(true, true, "192.168.0.0/16,www.yahoo.co.uk", "::ffff:212.82.100.150,::ffff:192.168.0.0/16")] <- fails on Max. www.yahoo.co.uk resolves to a different ip address.
- // [InlineData(true, false, "192.168.0.0/16,www.yahoo.co.uk", "212.82.100.150,192.168.0.0/16")]
- [InlineData(true, true, "192.168.t,127.0.0.1,1234.1232.12.1234", "::ffff:127.0.0.1")]
- [InlineData(true, false, "192.168.x,127.0.0.1,1234.1232.12.1234", "127.0.0.1")]
- [InlineData(true, true, "::1", "::1/128")]
- public void TestNetworks(bool ip4, bool ip6, string hostList, string match)
+ [MemberData(nameof(TestNetworks_TestData))]
+ public void TestNetworks(bool ip4, bool ip6, string[] hostList, IPAddress[] knownProxies, IPNetwork[] knownNetworks)
{
using var nm = CreateNetworkManager();
@@ -36,31 +81,25 @@ namespace Jellyfin.Server.Tests
EnableIPV6 = ip6
};
- var result = match + ",";
ForwardedHeadersOptions options = new ForwardedHeadersOptions();
// Need this here as ::1 and 127.0.0.1 are in them by default.
options.KnownProxies.Clear();
options.KnownNetworks.Clear();
- ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options);
+ ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList, options);
- var sb = new StringBuilder();
- foreach (var item in options.KnownProxies)
+ Assert.Equal(knownProxies.Length, options.KnownProxies.Count);
+ foreach (var item in knownProxies)
{
- sb.Append(item)
- .Append(',');
+ Assert.True(options.KnownProxies.Contains(item));
}
- foreach (var item in options.KnownNetworks)
+ Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count);
+ foreach (var item in knownNetworks)
{
- sb.Append(item.Prefix)
- .Append('/')
- .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture))
- .Append(',');
+ Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength));
}
-
- Assert.Equal(sb.ToString(), result);
}
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
diff --git a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
index 419afb2dc..d15c9d6f5 100644
--- a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
+++ b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs
@@ -12,9 +12,6 @@ namespace Jellyfin.Server.Tests
{
[Theory]
[InlineData("e0a72cb2a2c7", "e0a72cb2a2c7")] // isn't encoded
- [InlineData("random+test", "random test")] // encoded
- [InlineData("random%20test", "random test")] // encoded
- [InlineData("++", " ")] // encoded
public static void EmptyValueTest(string query, string key)
{
var dict = new Dictionary<string, StringValues>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 0a04a5c54..edf9e0fef 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -1,11 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <Nullable>enable</Nullable>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
@@ -16,11 +13,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs
index 357d61c0b..8019e0ab3 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Location/MovieNfoLocationTests.cs
@@ -1,8 +1,8 @@
-using System.Linq;
+using System;
+using System.Linq;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.System;
using MediaBrowser.XbmcMetadata.Savers;
using Xunit;
@@ -28,7 +28,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Location
var path2 = "/media/movies/Avengers Endgame/movie.nfo";
// uses ContainingFolderPath which uses Operating system specific paths
- if (MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows)
+ if (OperatingSystem.IsWindows())
{
movie.Path = movie.Path.Replace('/', '\\');
path1 = path1.Replace('/', '\\');
@@ -49,7 +49,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Location
var path2 = "/media/movies/Avengers Endgame/VIDEO_TS/VIDEO_TS.nfo";
// uses ContainingFolderPath which uses Operating system specific paths
- if (MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows)
+ if (OperatingSystem.IsWindows())
{
movie.Path = movie.Path.Replace('/', '\\');
path1 = path1.Replace('/', '\\');
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 30a48857a..7ea45d14d 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.System;
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
@@ -59,7 +58,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
_localImageFileMetadata = new FileSystemMetadata()
{
Exists = true,
- FullName = MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows ?
+ FullName = OperatingSystem.IsWindows() ?
"C:\\media\\movies\\Justice League (2017).jpg"
: "/media/movies/Justice League (2017).jpg"
};
@@ -208,6 +207,20 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
+ public void Parse_GivenFileWithFanartTag_Success()
+ {
+ var result = new MetadataResult<Video>()
+ {
+ Item = new Movie()
+ };
+
+ _parser.Fetch(result, "Test Data/Fanart.nfo", CancellationToken.None);
+
+ Assert.Single(result.RemoteImages.Where(x => x.type == ImageType.Backdrop));
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.type == ImageType.Backdrop).url);
+ }
+
+ [Fact]
public void Parse_RadarrUrlFile_Success()
{
var result = new MetadataResult<Video>()
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo
new file mode 100644
index 000000000..0b129bd8c
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Fanart.nfo
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<movie>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57b476a831d74.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57947e28cf10b.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5863d5c0cf0c9.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5a801747e5545.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5a801747e5545.png</thumb>
+ <thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5cd75683df92b.png</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-586017e95adbd.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5934d45bc6592.jpg</thumb>
+ <thumb aspect="banner" preview="https://assets.fanart.tv/preview/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-5aa9289a379fa.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585fb155c3743.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-585edbda91d82.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585edbda91d82.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5b86588882c12.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5b86588882c12.jpg</thumb>
+ <thumb aspect="landscape" preview="https://assets.fanart.tv/preview/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg">https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-5bbb9babe600c.jpg</thumb>
+ <thumb aspect="clearart" preview="https://assets.fanart.tv/preview/movies/141052/hdmovieclearart/justice-league-5865c23193041.png">https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a3af26360617.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-58690967b9765.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-58690967b9765.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a953ca4db6a6.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a0b913c233be.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png</thumb>
+ <thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-59dc595362ef1.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png</thumb>
+ <fanart>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-58fa1f1932897.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a14f5fd8dd16.jpg</thumb>
+ <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a119394ea362.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a119394ea362.jpg</thumb>
+ </fanart>
+ <thumb aspect="fanart">This-should-not-be-saved-as-a-fanart-image.jpg</thumb>
+</movie>