aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj6
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj4
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj4
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj4
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj4
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj4
-rw-r--r--tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs111
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj5
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs41
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj4
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj4
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs79
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs375
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs158
-rw-r--r--tests/Jellyfin.Providers.Tests/Test Data/Video/My.Video.mkv0
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs30
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs4
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj6
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj4
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs14
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo34
26 files changed, 735 insertions, 182 deletions
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 64e420c71..e04bfffbb 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,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="6.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 8564a0dd3..1ad0f4e00 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -12,10 +12,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
</ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 8971f72a4..f7f9c0361 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -12,11 +12,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 39fe0d1c5..a9935bbdb 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index 5e3ec8694..55125eb11 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -7,13 +7,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1">
+ <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
index 64bb15af2..c53dab6d8 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -7,13 +7,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1">
+ <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
index e3f0a7595..268631e58 100644
--- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
@@ -8,13 +8,13 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1">
+ <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 5959e0f7b..7a1d88ca6 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -21,8 +21,8 @@
<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.1" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 4eb598765..9da80c312 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -7,10 +7,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="3.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
</ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs
new file mode 100644
index 000000000..b396b5440
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/ExternalFiles/ExternalPathParserTests.cs
@@ -0,0 +1,111 @@
+using System.Text.RegularExpressions;
+using Emby.Naming.Common;
+using Emby.Naming.ExternalFiles;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Globalization;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests.ExternalFiles;
+
+public class ExternalPathParserTests
+{
+ private readonly ExternalPathParser _audioPathParser;
+ private readonly ExternalPathParser _subtitlePathParser;
+
+ public ExternalPathParserTests()
+ {
+ var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" });
+ var frenchCultureDto = new CultureDto("French", "French", "fr", new[] { "fre", "fra" });
+
+ var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose);
+ localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase)))
+ .Returns(englishCultureDto);
+ localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"fr.*", RegexOptions.IgnoreCase)))
+ .Returns(frenchCultureDto);
+
+ _audioPathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Audio);
+ _subtitlePathParser = new ExternalPathParser(new NamingOptions(), localizationManager.Object, DlnaProfileType.Subtitle);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("MyVideo.ass")]
+ [InlineData("MyVideo.mks")]
+ [InlineData("MyVideo.sami")]
+ [InlineData("MyVideo.srt")]
+ [InlineData("MyVideo.m4v")]
+ public void ParseFile_AudioExtensionsNotMatched_ReturnsNull(string path)
+ {
+ Assert.Null(_audioPathParser.ParseFile(path, string.Empty));
+ }
+
+ [Theory]
+ [InlineData("MyVideo.aa")]
+ [InlineData("MyVideo.aac")]
+ [InlineData("MyVideo.flac")]
+ [InlineData("MyVideo.m4a")]
+ [InlineData("MyVideo.mka")]
+ [InlineData("MyVideo.mp3")]
+ public void ParseFile_AudioExtensionsMatched_ReturnsPath(string path)
+ {
+ var actual = _audioPathParser.ParseFile(path, string.Empty);
+ Assert.NotNull(actual);
+ Assert.Equal(path, actual!.Path);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("MyVideo.aa")]
+ [InlineData("MyVideo.aac")]
+ [InlineData("MyVideo.flac")]
+ [InlineData("MyVideo.mka")]
+ [InlineData("MyVideo.m4v")]
+ public void ParseFile_SubtitleExtensionsNotMatched_ReturnsNull(string path)
+ {
+ Assert.Null(_subtitlePathParser.ParseFile(path, string.Empty));
+ }
+
+ [Theory]
+ [InlineData("MyVideo.ass")]
+ [InlineData("MyVideo.mks")]
+ [InlineData("MyVideo.sami")]
+ [InlineData("MyVideo.srt")]
+ [InlineData("MyVideo.vtt")]
+ public void ParseFile_SubtitleExtensionsMatched_ReturnsPath(string path)
+ {
+ var actual = _subtitlePathParser.ParseFile(path, string.Empty);
+ Assert.NotNull(actual);
+ Assert.Equal(path, actual!.Path);
+ }
+
+ [Theory]
+ [InlineData("", null, null)]
+ [InlineData(".default", null, null, true, false)]
+ [InlineData(".forced", null, null, false, true)]
+ [InlineData(".foreign", null, null, false, true)]
+ [InlineData(".default.forced", null, null, true, true)]
+ [InlineData(".forced.default", null, null, true, true)]
+ [InlineData(".DEFAULT.FORCED", null, null, true, true)]
+ [InlineData(".en", null, "eng")]
+ [InlineData(".EN", null, "eng")]
+ [InlineData(".fr.en", "fr", "eng")]
+ [InlineData(".en.fr", "en", "fre")]
+ [InlineData(".title.en.fr", "title.en", "fre")]
+ [InlineData(".Title Goes Here", "Title Goes Here", null)]
+ [InlineData(".Title.with.Separator", "Title.with.Separator", null)]
+ [InlineData(".title.en.default.forced", "title", "eng", true, true)]
+ [InlineData(".forced.default.en.title", "title", "eng", true, true)]
+ public void ParseFile_ExtraTokens_ParseToValues(string tokens, string? title, string? language, bool isDefault = false, bool isForced = false)
+ {
+ var path = "My.Video" + tokens + ".srt";
+
+ var actual = _subtitlePathParser.ParseFile(path, tokens);
+
+ Assert.NotNull(actual);
+ Assert.Equal(title, actual!.Title);
+ Assert.Equal(language, actual.Language);
+ Assert.Equal(isDefault, actual.IsDefault);
+ Assert.Equal(isForced, actual.IsForced);
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 53587205c..cc3d4faa0 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -12,10 +12,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
deleted file mode 100644
index 2446660f3..000000000
--- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Emby.Naming.Common;
-using Emby.Naming.Subtitles;
-using Xunit;
-
-namespace Jellyfin.Naming.Tests.Subtitles
-{
- public class SubtitleParserTests
- {
- private readonly NamingOptions _namingOptions = new NamingOptions();
-
- [Theory]
- [InlineData("The Skin I Live In (2011).srt", null, false, false)]
- [InlineData("The Skin I Live In (2011).eng.srt", "eng", false, false)]
- [InlineData("The Skin I Live In (2011).eng.default.srt", "eng", true, false)]
- [InlineData("The Skin I Live In (2011).eng.forced.srt", "eng", false, true)]
- [InlineData("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true)]
- [InlineData("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true)]
- [InlineData("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true)]
- public void SubtitleParser_ValidFileName_Parses(string input, string language, bool isDefault, bool isForced)
- {
- var parser = new SubtitleParser(_namingOptions);
-
- var result = parser.ParseFile(input);
-
- Assert.Equal(language, result?.Language, true);
- Assert.Equal(isDefault, result?.IsDefault);
- Assert.Equal(isForced, result?.IsForced);
- Assert.Equal(input, result?.Path);
- }
-
- [Theory]
- [InlineData("The Skin I Live In (2011).mp4")]
- [InlineData("")]
- public void SubtitleParser_InvalidFileName_ReturnsNull(string input)
- {
- var parser = new SubtitleParser(_namingOptions);
-
- Assert.Null(parser.ParseFile(input));
- }
- }
-}
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 1b6635ead..00aac8965 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -12,10 +12,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="3.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index cf130ff0d..5531049f5 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -13,14 +13,14 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1">
+ <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs
new file mode 100644
index 000000000..381d6c72d
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Naming.Common;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Providers.MediaInfo;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo;
+
+public class AudioResolverTests
+{
+ private readonly AudioResolver _audioResolver;
+
+ public AudioResolverTests()
+ {
+ // prep BaseItem and Video for calls made that expect managers
+ Video.LiveTvManager = Mock.Of<ILiveTvManager>();
+
+ var applicationPaths = new Mock<IServerApplicationPaths>().Object;
+ var serverConfig = new Mock<IServerConfigurationManager>();
+ serverConfig.Setup(c => c.ApplicationPaths)
+ .Returns(applicationPaths);
+ BaseItem.ConfigurationManager = serverConfig.Object;
+
+ // build resolver to test with
+ var localizationManager = Mock.Of<ILocalizationManager>();
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
+ .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
+ {
+ MediaStreams = new List<MediaStream>
+ {
+ new()
+ }
+ }));
+
+ _audioResolver = new AudioResolver(localizationManager, mediaEncoder.Object, new NamingOptions());
+ }
+
+ [Theory]
+ [InlineData("My.Video.srt", false, false)]
+ [InlineData("My.Video.mp3", false, true)]
+ [InlineData("My.Video.srt", true, false)]
+ [InlineData("My.Video.mp3", true, true)]
+ public async void GetExternalStreams_MixedFilenames_PicksAudio(string file, bool metadataDirectory, bool matches)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
+ {
+ Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv"
+ };
+
+ var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory);
+ var streams = await _audioResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
+
+ if (matches)
+ {
+ Assert.Single(streams);
+ var actual = streams[0];
+ Assert.Equal(MediaStreamType.Audio, actual.Type);
+ }
+ else
+ {
+ Assert.Empty(streams);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs
new file mode 100644
index 000000000..926ec5c91
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/MediaInfoResolverTests.cs
@@ -0,0 +1,375 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Naming.Common;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Providers.MediaInfo;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo;
+
+public class MediaInfoResolverTests
+{
+ public const string VideoDirectoryPath = "Test Data/Video";
+ private const string VideoDirectoryRegex = @"Test Data[/\\]Video";
+ private const string MetadataDirectoryPath = "library/00/00000000000000000000000000000000";
+ private const string MetadataDirectoryRegex = @"library.*";
+
+ private readonly ILocalizationManager _localizationManager;
+ private readonly MediaInfoResolver _subtitleResolver;
+
+ public MediaInfoResolverTests()
+ {
+ // prep BaseItem and Video for calls made that expect managers
+ Video.LiveTvManager = Mock.Of<ILiveTvManager>();
+
+ var applicationPaths = new Mock<IServerApplicationPaths>().Object;
+ var serverConfig = new Mock<IServerConfigurationManager>();
+ serverConfig.Setup(c => c.ApplicationPaths)
+ .Returns(applicationPaths);
+ BaseItem.ConfigurationManager = serverConfig.Object;
+
+ // build resolver to test with
+ var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" });
+
+ var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose);
+ localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase)))
+ .Returns(englishCultureDto);
+ _localizationManager = localizationManager.Object;
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
+ .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
+ {
+ MediaStreams = new List<MediaStream>
+ {
+ new()
+ }
+ }));
+
+ _subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, new NamingOptions());
+ }
+
+ [Theory]
+ [InlineData("https://url.com/My.Video.mkv")]
+ [InlineData("non-existent/path")]
+ public void GetExternalFiles_BadPaths_ReturnsNoSubtitles(string path)
+ {
+ // need a media source manager capable of returning something other than file protocol
+ var mediaSourceManager = new Mock<IMediaSourceManager>();
+ mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*")))
+ .Returns(MediaProtocol.Http);
+ BaseItem.MediaSourceManager = mediaSourceManager.Object;
+
+ var video = new Movie
+ {
+ Path = path
+ };
+
+ var files = _subtitleResolver.GetExternalFiles(video, Mock.Of<IDirectoryService>(), false);
+
+ Assert.Empty(files);
+ }
+
+ [Theory]
+ [InlineData("My.Video.srt", null)] // exact
+ [InlineData("My.Video.en.srt", "eng")]
+ [InlineData("MyVideo.en.srt", "eng")] // shorter title
+ [InlineData("My _ Video.en.srt", "eng")] // longer title
+ [InlineData("My.Video.en.srt", "eng", true)]
+ public void GetExternalFiles_FuzzyMatching_MatchesAndParsesToken(string file, string? language, bool metadataDirectory = false)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
+ {
+ Path = VideoDirectoryPath + "/My.Video.mkv"
+ };
+
+ var directoryService = GetDirectoryServiceForExternalFile(file, metadataDirectory);
+ var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList();
+
+ Assert.Single(streams);
+ var actual = streams[0];
+ Assert.Equal(language, actual.Language);
+ Assert.Null(actual.Title);
+ }
+
+ [Theory]
+ [InlineData("My.Video.mp3")]
+ [InlineData("My.Video.png")]
+ [InlineData("My.Video.txt")]
+ [InlineData("My.Video Sequel.srt")]
+ [InlineData("Some.Other.Video.srt")]
+ public void GetExternalFiles_FuzzyMatching_RejectsNonMatches(string file)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
+ {
+ Path = VideoDirectoryPath + "/My.Video.mkv"
+ };
+
+ var directoryService = GetDirectoryServiceForExternalFile(file);
+ var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList();
+
+ Assert.Empty(streams);
+ }
+
+ [Theory]
+ [InlineData("https://url.com/My.Video.mkv")]
+ [InlineData("non-existent/path")]
+ [InlineData(VideoDirectoryPath)] // valid but no files found for this test
+ public async void GetExternalStreams_BadPaths_ReturnsNoSubtitles(string path)
+ {
+ // need a media source manager capable of returning something other than file protocol
+ var mediaSourceManager = new Mock<IMediaSourceManager>();
+ mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*")))
+ .Returns(MediaProtocol.Http);
+ BaseItem.MediaSourceManager = mediaSourceManager.Object;
+
+ var video = new Movie
+ {
+ Path = path
+ };
+
+ var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(Array.Empty<string>());
+
+ var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict);
+
+ var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, new NamingOptions());
+
+ var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None);
+
+ Assert.Empty(streams);
+ }
+
+ private static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data()
+ {
+ var data = new TheoryData<string, MediaStream[], MediaStream[]>();
+
+ // filename and stream have no metadata set
+ string file = "My.Video.srt";
+ data.Add(
+ file,
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
+ },
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
+ });
+
+ // filename has metadata
+ file = "My.Video.Title1.default.forced.en.srt";
+ data.Add(
+ file,
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
+ },
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title1", 0, true, true)
+ });
+
+ // single stream with metadata
+ file = "My.Video.mks";
+ data.Add(
+ file,
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true)
+ },
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true)
+ });
+
+ // stream wins for title/language, filename wins for flags when conflicting
+ file = "My.Video.Title2.default.forced.en.srt";
+ data.Add(
+ file,
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0)
+ },
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0, true, true)
+ });
+
+ // multiple stream with metadata - filename flags ignored but other data filled in when missing from stream
+ file = "My.Video.Title3.default.forced.en.srt";
+ data.Add(
+ file,
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0, true, true),
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1)
+ },
+ new[]
+ {
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title3", 0, true, true),
+ CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1)
+ });
+
+ return data;
+ }
+
+ [Theory]
+ [MemberData(nameof(GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data))]
+ public async void GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly(string file, MediaStream[] inputStreams, MediaStream[] expectedStreams)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
+ {
+ Path = VideoDirectoryPath + "/My.Video.mkv"
+ };
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
+ .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
+ {
+ MediaStreams = inputStreams.ToList()
+ }));
+
+ var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, new NamingOptions());
+
+ var directoryService = GetDirectoryServiceForExternalFile(file);
+ var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
+
+ Assert.Equal(expectedStreams.Length, streams.Count);
+ for (var i = 0; i < expectedStreams.Length; i++)
+ {
+ var expected = expectedStreams[i];
+ var actual = streams[i];
+
+ Assert.True(actual.IsExternal);
+ Assert.Equal(expected.Index, actual.Index);
+ Assert.Equal(expected.Type, actual.Type);
+ Assert.Equal(expected.Path, actual.Path);
+ Assert.Equal(expected.IsDefault, actual.IsDefault);
+ Assert.Equal(expected.IsForced, actual.IsForced);
+ Assert.Equal(expected.Language, actual.Language);
+ Assert.Equal(expected.Title, actual.Title);
+ }
+ }
+
+ [Theory]
+ [InlineData(1, 1)]
+ [InlineData(1, 2)]
+ [InlineData(2, 1)]
+ [InlineData(2, 2)]
+ public async void GetExternalStreams_StreamIndex_HandlesFilesAndContainers(int fileCount, int streamCount)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
+ {
+ Path = VideoDirectoryPath + "/My.Video.mkv"
+ };
+
+ var files = new string[fileCount];
+ for (int i = 0; i < fileCount; i++)
+ {
+ files[i] = $"{VideoDirectoryPath}/MyVideo.{i}.srt";
+ }
+
+ var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(files);
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(Array.Empty<string>());
+
+ List<MediaStream> GenerateMediaStreams()
+ {
+ var mediaStreams = new List<MediaStream>();
+ for (int i = 0; i < streamCount; i++)
+ {
+ mediaStreams.Add(new());
+ }
+
+ return mediaStreams;
+ }
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
+ .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
+ {
+ MediaStreams = GenerateMediaStreams()
+ }));
+
+ var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, new NamingOptions());
+
+ int startIndex = 1;
+ var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None);
+
+ Assert.Equal(fileCount * streamCount, streams.Count);
+ for (var i = 0; i < streams.Count; i++)
+ {
+ Assert.Equal(startIndex + i, streams[i].Index);
+ // intentional integer division to ensure correct number of streams come back from each file
+ Assert.Matches(@$".*\.{i / streamCount}\.srt", streams[i].Path);
+ }
+ }
+
+ private static MediaStream CreateMediaStream(string path, string? language, string? title, int index, bool isForced = false, bool isDefault = false)
+ {
+ return new MediaStream
+ {
+ Index = index,
+ Type = MediaStreamType.Subtitle,
+ Path = path,
+ IsDefault = isDefault,
+ IsForced = isForced,
+ Language = language,
+ Title = title
+ };
+ }
+
+ /// <summary>
+ /// Provides an <see cref="IDirectoryService"/> that when queried for the test video/metadata directory will return a path including the provided file name.
+ /// </summary>
+ /// <param name="file">The name of the file to locate.</param>
+ /// <param name="useMetadataDirectory"><c>true</c> if the file belongs in the metadata directory.</param>
+ /// <returns>A mocked <see cref="IDirectoryService"/>.</returns>
+ public static IDirectoryService GetDirectoryServiceForExternalFile(string file, bool useMetadataDirectory = false)
+ {
+ var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
+ if (useMetadataDirectory)
+ {
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(Array.Empty<string>());
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(new[] { MetadataDirectoryPath + "/" + file });
+ }
+ else
+ {
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(new[] { VideoDirectoryPath + "/" + file });
+ directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
+ .Returns(Array.Empty<string>());
+ }
+
+ return directoryService.Object;
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
index 040ea5d1d..0f1086f59 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
@@ -1,129 +1,79 @@
-#pragma warning disable CA1002 // Do not expose generic lists
-
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Naming.Common;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Providers.MediaInfo;
using Moq;
using Xunit;
-namespace Jellyfin.Providers.Tests.MediaInfo
+namespace Jellyfin.Providers.Tests.MediaInfo;
+
+public class SubtitleResolverTests
{
- public class SubtitleResolverTests
- {
- public static TheoryData<List<MediaStream>, string, int, string[], MediaStream[]> AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData()
- {
- var data = new TheoryData<List<MediaStream>, string, int, string[], MediaStream[]>();
+ private readonly SubtitleResolver _subtitleResolver;
- var index = 0;
- data.Add(
- new List<MediaStream>(),
- "/video/My.Video.mkv",
- index,
- new[]
- {
- "/video/My.Video.mp3",
- "/video/My.Video.png",
- "/video/My.Video.srt",
- "/video/My.Video.txt",
- "/video/My.Video.vtt",
- "/video/My.Video.ass",
- "/video/My.Video.sub",
- "/video/My.Video.ssa",
- "/video/My.Video.smi",
- "/video/My.Video.sami",
- "/video/My.Video.en.srt",
- "/video/My.Video.default.en.srt",
- "/video/My.Video.default.forced.en.srt",
- "/video/My.Video.en.default.forced.srt",
- "/video/My.Video.With.Additional.Garbage.en.srt",
- "/video/My.Video With Additional Garbage.srt"
- },
- new[]
- {
- CreateMediaStream("/video/My.Video.srt", "srt", null, index++),
- CreateMediaStream("/video/My.Video.vtt", "vtt", null, index++),
- CreateMediaStream("/video/My.Video.ass", "ass", null, index++),
- CreateMediaStream("/video/My.Video.sub", "sub", null, index++),
- CreateMediaStream("/video/My.Video.ssa", "ssa", null, index++),
- CreateMediaStream("/video/My.Video.smi", "smi", null, index++),
- CreateMediaStream("/video/My.Video.sami", "sami", null, index++),
- CreateMediaStream("/video/My.Video.en.srt", "srt", "en", index++),
- CreateMediaStream("/video/My.Video.default.en.srt", "srt", "en", index++, isDefault: true),
- 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),
- });
+ public SubtitleResolverTests()
+ {
+ // prep BaseItem and Video for calls made that expect managers
+ Video.LiveTvManager = Mock.Of<ILiveTvManager>();
- return data;
- }
+ var applicationPaths = new Mock<IServerApplicationPaths>().Object;
+ var serverConfig = new Mock<IServerConfigurationManager>();
+ serverConfig.Setup(c => c.ApplicationPaths)
+ .Returns(applicationPaths);
+ BaseItem.ConfigurationManager = serverConfig.Object;
- [Theory]
- [MemberData(nameof(AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles_TestData))]
- public void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles(List<MediaStream> streams, string videoPath, int startIndex, string[] files, MediaStream[] expectedResult)
- {
- new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
+ // build resolver to test with
+ var localizationManager = Mock.Of<ILocalizationManager>();
- Assert.Equal(expectedResult.Length, streams.Count);
- for (var i = 0; i < expectedResult.Length; i++)
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
+ .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
{
- var expected = expectedResult[i];
- var actual = streams[i];
+ MediaStreams = new List<MediaStream>
+ {
+ new()
+ }
+ }));
- 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);
- }
- }
+ _subtitleResolver = new SubtitleResolver(localizationManager, mediaEncoder.Object, new NamingOptions());
+ }
- [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)
+ [Theory]
+ [InlineData("My.Video.srt", false, true)]
+ [InlineData("My.Video.mp3", false, false)]
+ [InlineData("My.Video.srt", true, true)]
+ [InlineData("My.Video.mp3", true, false)]
+ public async void GetExternalStreams_MixedFilenames_PicksSubtitles(string file, bool metadataDirectory, bool matches)
+ {
+ BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
+
+ var video = new Movie
{
- var streams = new List<MediaStream>();
- var expected = CreateMediaStream(file, codec, language, 0, isForced, isDefault);
+ Path = MediaInfoResolverTests.VideoDirectoryPath + "/My.Video.mkv"
+ };
- new SubtitleResolver(Mock.Of<ILocalizationManager>()).AddExternalSubtitleStreams(streams, videoPath, 0, new[] { file });
+ var directoryService = MediaInfoResolverTests.GetDirectoryServiceForExternalFile(file, metadataDirectory);
+ var streams = await _subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
+ if (matches)
+ {
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);
+ Assert.Equal(MediaStreamType.Subtitle, actual.Type);
}
-
- private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false)
+ else
{
- return new()
- {
- Index = index,
- Codec = codec,
- Type = MediaStreamType.Subtitle,
- IsExternal = true,
- Path = path,
- IsDefault = isDefault,
- IsForced = isForced,
- Language = language
- };
+ Assert.Empty(streams);
}
}
}
diff --git a/tests/Jellyfin.Providers.Tests/Test Data/Video/My.Video.mkv b/tests/Jellyfin.Providers.Tests/Test Data/Video/My.Video.mkv
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/Test Data/Video/My.Video.mkv
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 dcba0fefe..066112dcb 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -21,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="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs
new file mode 100644
index 000000000..d59f2f4e5
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaStreamSelectorTests.cs
@@ -0,0 +1,30 @@
+using System;
+using Emby.Server.Implementations.Library;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library;
+
+public class MediaStreamSelectorTests
+{
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void GetDefaultAudioStreamIndex_EmptyStreams_Null(bool preferDefaultTrack)
+ {
+ Assert.Null(MediaStreamSelector.GetDefaultAudioStreamIndex(Array.Empty<MediaStream>(), Array.Empty<string>(), preferDefaultTrack));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void GetDefaultAudioStreamIndex_WithoutDefault_NotNull(bool preferDefaultTrack)
+ {
+ var streams = new[]
+ {
+ new MediaStream()
+ };
+
+ Assert.NotNull(MediaStreamSelector.GetDefaultAudioStreamIndex(streams, Array.Empty<string>(), preferDefaultTrack));
+ }
+}
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 8425cdf33..43e38ea6e 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,13 +9,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="6.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
index 3d34a18e7..adaf624a9 100644
--- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
@@ -7,11 +7,11 @@ using MediaBrowser.Common;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
+using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Jellyfin.Server.Integration.Tests
{
@@ -74,7 +74,7 @@ namespace Jellyfin.Server.Integration.Tests
appPaths,
loggerFactory,
commandLineOpts,
- new ConfigurationBuilder().Build());
+ startupConfig);
_disposableComponents.Add(appHost);
var serviceCollection = new ServiceCollection();
appHost.Init(serviceCollection);
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index f65faa27e..9576f6a11 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,12 +10,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="6.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index a0aa51dc9..f34dbc922 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -13,11 +13,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.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.1.1" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index 3e726f23d..4f4ae5afb 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -123,6 +123,20 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
+ public void Parse_GivenFileWithThumbWithoutAspect_Success()
+ {
+ var result = new MetadataResult<Episode>
+ {
+ Item = new Episode()
+ };
+
+ _parser.Fetch(result, "Test Data/Sonarr-Thumb.nfo", CancellationToken.None);
+
+ Assert.Single(result.RemoteImages.Where(x => x.Type == ImageType.Primary));
+ Assert.Equal("https://artworks.thetvdb.com/banners/episodes/359095/7081317.jpg", result.RemoteImages.First(x => x.Type == ImageType.Primary).Url);
+ }
+
+ [Fact]
public void Fetch_WithNullItem_ThrowsArgumentException()
{
var result = new MetadataResult<Episode>();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo
new file mode 100644
index 000000000..fb86768ef
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Sonarr-Thumb.nfo
@@ -0,0 +1,34 @@
+<episodedetails>
+ <title>Sometimes a Genius's Every Action Is at the Mercy of X</title>
+ <season>1</season>
+ <episode>8</episode>
+ <aired>2019-05-26</aired>
+ <plot>After Nariyuki wins a smartphone in a lottery, he can't wait to use it for apps like the dictionary, schedule managing, and the like. He also learns that studying in the bathtub is effective and quickly puts the method into practice.</plot>
+ <uniqueid type="sonarr" default="true">4289</uniqueid>
+ <thumb>https://artworks.thetvdb.com/banners/episodes/359095/7081317.jpg</thumb>
+ <watched>false</watched>
+ <fileinfo>
+ <streamdetails>
+ <video>
+ <aspect>1.77777779</aspect>
+ <bitrate>2208901</bitrate>
+ <codec>x265</codec>
+ <framerate>23.976</framerate>
+ <height>1080</height>
+ <scantype></scantype>
+ <width>1920</width>
+ <duration>23.683416666666666</duration>
+ <durationinseconds>1421</durationinseconds>
+ </video>
+ <audio>
+ <bitrate>1468567</bitrate>
+ <channels>2</channels>
+ <codec>FLAC</codec>
+ <language>Japanese / Japanese</language>
+ </audio>
+ <subtitle>
+ <language>English</language>
+ </subtitle>
+ </streamdetails>
+ </fileinfo>
+</episodedetails>